jackrabbit-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ang...@apache.org
Subject svn commit: r950440 [2/3] - in /jackrabbit/trunk/jackrabbit-core/src: main/java/org/apache/jackrabbit/core/ main/java/org/apache/jackrabbit/core/observation/ main/java/org/apache/jackrabbit/core/query/lucene/ main/java/org/apache/jackrabbit/core/securi...
Date Wed, 02 Jun 2010 09:00:46 GMT
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java?rev=950440&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java Wed Jun  2 09:00:44 2010
@@ -0,0 +1,251 @@
+/*
+ * 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.core.security.authorization.acl;
+
+import org.apache.commons.collections.map.LRUMap;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.security.AccessControlEntry;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <code>CachingEntryCollector</code> extends <code>EntryCollector</code> by
+ * keeping a cache of ACEs per access controlled nodeId.
+ */
+class CachingEntryCollector extends EntryCollector {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(CachingEntryCollector.class);
+
+    /**
+     * Cache to look up the list of access control entries defined at a given
+     * nodeID (key). The map only contains an entry if the corresponding Node
+     * is access controlled.
+     */
+    //private final Map<NodeId, List<AccessControlEntry>> cache;
+    private final Map<NodeId, CacheEntry> cache;
+    private final Object monitor = new Object();
+
+    /**
+     * 
+     * @param systemSession
+     * @param systemEditor
+     * @param rootID
+     * @throws RepositoryException
+     */
+    @SuppressWarnings("unchecked")    
+    CachingEntryCollector(SessionImpl systemSession, ACLEditor systemEditor, NodeId rootID) throws RepositoryException {
+        super(systemSession, systemEditor, rootID);
+        
+        cache = new LRUMap(1000);
+    }
+
+    @Override
+    protected void close() {
+        super.close();
+        synchronized (monitor) {
+            cache.clear();
+        }
+    }
+
+    //-----------------------------------------------------< EntryCollector >---
+    /**
+     * @see EntryCollector#getEntries(org.apache.jackrabbit.core.NodeImpl)
+     */
+    @Override    
+    protected List<AccessControlEntry> getEntries(NodeImpl node) throws RepositoryException {
+        List<AccessControlEntry> entries;
+        NodeId nodeId = node.getNodeId();
+        synchronized (monitor) {
+            CacheEntry ce = cache.get(nodeId);
+            if (ce != null) {
+                entries = ce.entries;
+            } else {
+                // fetch entries and update the cache
+                entries = updateCache(node);
+            }
+        }
+        return entries;
+    }
+    
+    /**
+     * @see EntryCollector#getEntries(org.apache.jackrabbit.core.id.NodeId)
+     */
+    @Override
+    protected List<AccessControlEntry> getEntries(NodeId nodeId) throws RepositoryException {
+        List<AccessControlEntry> entries;
+        synchronized (monitor) {
+            CacheEntry ce = cache.get(nodeId);
+            if (ce != null) {
+                entries = ce.entries;
+            } else {
+                // fetch entries and update the cache
+                NodeImpl n = getNodeById(nodeId);
+                entries = updateCache(n);
+            }
+        }
+        return entries;
+    }
+
+    /**
+     *
+     * @param node
+     * @return
+     * @throws RepositoryException
+     */
+    private List<AccessControlEntry> updateCache(NodeImpl node) throws RepositoryException {
+        List<AccessControlEntry> entries = super.getEntries(node);
+        if (!entries.isEmpty()) {
+            // find the next access control ancestor in the hierarchy
+            // 'null' indicates that there is no ac-controlled ancestor.
+            NodeId nextId = null;
+            NodeImpl n = node;            
+            while (nextId == null && !rootID.equals(n.getNodeId())) {
+                if (cache.containsKey(n.getNodeId())) {
+                    nextId = n.getNodeId();
+                } else if (cache.containsKey(n.getParentId())) {
+                    nextId = n.getParentId();
+                } else {
+                    n = (NodeImpl) n.getParent();
+                    if (hasEntries(n)) {
+                        nextId = n.getNodeId();
+                    } // else: not access controlled -> test next ancestors
+                }
+            }
+
+            // build a new cacheEntry and add it to the cache
+            CacheEntry ce = new CacheEntry(entries, nextId);
+            cache.put(node.getNodeId(), ce);
+            
+            log.debug("Update cache for node with ID {0}: {1}", node, ce);
+        } // else: not access controlled -> ignore.
+        return entries;
+    }
+
+    /**
+     * Evaluates if the given node is access controlled and holds a non-empty
+     * rep:policy child node.
+     * 
+     * @param n
+     * @return
+     * @throws RepositoryException
+     */
+    private static boolean hasEntries(NodeImpl n) throws RepositoryException {
+        if (ACLProvider.isAccessControlled(n)) {
+            // collect the aces of that node.
+            NodeImpl aclNode = n.getNode(N_POLICY);
+            return aclNode.hasNodes();
+        }
+
+        // no acl defined here
+        return false;
+    }
+
+    /**
+     * Returns the id of the next access-controlled ancestor if the specified
+     * is contained in the cache. Otherwise the method of the super-class is called.
+     *
+     * @param nodeId
+     * @return
+     * @throws RepositoryException
+     * @see EntryCollector#getParentId(org.apache.jackrabbit.core.id.NodeId)
+     */
+    @Override
+    protected NodeId getParentId(NodeId nodeId) throws RepositoryException {
+        synchronized (monitor) {
+            CacheEntry ce = cache.get(nodeId);
+            if (ce != null) {
+                return ce.nextAcNodeId;
+            } else {
+                // no cache entry
+                return super.getParentId(nodeId);
+            }
+        }
+    }
+
+    /**
+     * @see EntryCollector#notifyListeners(org.apache.jackrabbit.core.security.authorization.AccessControlModifications)
+     */
+    @Override
+    public void notifyListeners(AccessControlModifications modifications) {
+        /* Update cache for all affected access controlled nodes */
+        for (Object key : modifications.getNodeIdentifiers()) {
+            if (!(key instanceof NodeId)) {
+                log.warn("Cannot process AC modificationMap entry. Keys must be NodeId.");
+                continue;
+            }
+            NodeId nodeId = (NodeId) key;
+            int type = modifications.getType(nodeId);
+            synchronized (monitor) {
+                if ((type & POLICY_ADDED) == POLICY_ADDED) {
+                    // clear the complete cache since the nextAcNodeId may
+                    // have changed due to the added acl.
+                    cache.clear();
+                    break; // no need for further processing.
+                } else if ((type & POLICY_REMOVED) == POLICY_REMOVED) {
+                    // clear the entry and change the entries having a nextID
+                    // pointing to this node.
+                    CacheEntry ce = cache.remove(nodeId);
+                    if (ce != null) {
+                        NodeId nextId = ce.nextAcNodeId;
+                        for (CacheEntry entry : cache.values()) {
+                            if (nodeId.equals(entry.nextAcNodeId)) {
+                                entry.nextAcNodeId = nextId;
+                            }
+                        }
+                    }
+                } else if ((type & POLICY_MODIFIED) == POLICY_MODIFIED) {
+                    // simply clear the cache entry -> reload upon next access.
+                    cache.remove(nodeId);
+                }
+            }
+        }
+        super.notifyListeners(modifications);
+    }
+
+    //--------------------------------------------------------------------------
+    /**
+     *
+     */
+    private class CacheEntry {
+
+        private final List<AccessControlEntry> entries;
+        private NodeId nextAcNodeId;
+
+        private CacheEntry(List<AccessControlEntry> entries, NodeId nextAcNodeId) {
+            this.entries = entries;
+            this.nextAcNodeId = nextAcNodeId;
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder();
+            sb.append("size = ").append(entries.size()).append(", ");
+            sb.append("nextAcNodeId = ").append(nextAcNodeId);
+            return sb.toString();
+        }
+    }
+}
\ No newline at end of file

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/CachingEntryCollector.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java?rev=950440&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java Wed Jun  2 09:00:44 2010
@@ -0,0 +1,294 @@
+/*
+ * 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.core.security.authorization.acl;
+
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
+import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
+import org.apache.jackrabbit.core.security.authorization.AccessControlObserver;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.security.AccessControlEntry;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <code>EntryCollector</code> collects ACEs defined and effective for a
+ * given <code>Node</code> and listens to access control modifications in order
+ * to inform listeners.
+ */
+public class EntryCollector extends AccessControlObserver implements AccessControlConstants {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(EntryCollector.class);
+        
+    protected final SessionImpl systemSession;
+    protected final NodeId rootID;
+    
+    private final ACLEditor systemEditor;
+       
+    private final String repPolicyName;
+
+    /**
+     *
+     * @param systemSession
+     * @param systemEditor
+     * @param rootID
+     * @throws RepositoryException
+     */
+    protected EntryCollector(SessionImpl systemSession, ACLEditor systemEditor, NodeId rootID) throws RepositoryException {
+        this.systemSession = systemSession;
+        this.systemEditor = systemEditor;
+        this.rootID = rootID;
+        repPolicyName = systemSession.getJCRName(N_POLICY);
+
+        ObservationManager observationMgr = systemSession.getWorkspace().getObservationManager();
+        /*
+         Make sure the collector and all subscribed listeners are informed upon
+         ACL modifications. Interesting events are:
+         - new ACL (NODE_ADDED)
+         - new ACE (NODE_ADDED)
+         - changing ACE (PROPERTY_CHANGED)
+         - removed ACL (NODE_REMOVED)
+         - removed ACE (NODE_REMOVED)
+        */
+        int events = Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED;
+        String[] ntNames = new String[] {
+                systemSession.getJCRName(NT_REP_ACCESS_CONTROLLABLE),
+                systemSession.getJCRName(NT_REP_ACL),
+                systemSession.getJCRName(NT_REP_ACE)
+        };
+        observationMgr.addEventListener(this, events, systemSession.getRootNode().getPath(), true, null, ntNames, true);        
+    }
+
+    /**
+     * Release all resources contained by this instance. It will no longer be
+     * used. This implementation only stops listening to ac modification events.
+     */
+    protected void close() {
+        super.close();
+        try {
+            systemSession.getWorkspace().getObservationManager().removeEventListener(this);
+        } catch (RepositoryException e) {
+            log.error("Unexpected error while closing CachingEntryCollector", e);
+        }
+    }
+
+    /**
+     * Collect the ACEs effective at the given node applying the specified
+     * filter.
+     * 
+     * @param node
+     * @param filter
+     * @return
+     * @throws RepositoryException
+     */
+    protected List<AccessControlEntry> collectEntries(NodeImpl node, EntryFilter filter) throws RepositoryException {
+        LinkedList<AccessControlEntry> userAces = new LinkedList<AccessControlEntry>();
+        LinkedList<AccessControlEntry> groupAces = new LinkedList<AccessControlEntry>();
+
+        NodeId next = node.getNodeId();
+        while (next != null) {
+            List<AccessControlEntry> entries = getEntries(next);
+            if (!entries.isEmpty() && filter != null) {
+                filter.filterEntries(entries, userAces, groupAces);
+            }
+            next = getParentId(next);
+        }
+        
+        List<AccessControlEntry> entries = new ArrayList(userAces.size() + groupAces.size());
+        entries.addAll(userAces);
+        entries.addAll(groupAces);
+
+        return entries;
+    }
+
+    /**
+     * Retrieve the access control entries defined for the given node. If the
+     * node is not access controlled or if the ACL is empty this method returns
+     * an empty list.
+     * 
+     * @param node
+     * @return
+     * @throws RepositoryException
+     */
+    protected List<AccessControlEntry> getEntries(NodeImpl node) throws RepositoryException {
+        List<AccessControlEntry> entries;
+        if (ACLProvider.isAccessControlled(node)) {
+            // collect the aces of that node.
+            NodeImpl aclNode = node.getNode(N_POLICY);
+            entries = Arrays.asList(systemEditor.getACL(aclNode).getAccessControlEntries());
+        } else {
+            // not access controlled
+            entries = Collections.emptyList();
+        }
+        return entries;
+    }
+
+    /**
+     * 
+     * @param nodeId
+     * @return
+     * @throws RepositoryException
+     */
+    protected List<AccessControlEntry> getEntries(NodeId nodeId) throws RepositoryException {
+        NodeImpl node = getNodeById(nodeId);
+        return getEntries(node);
+    }
+
+    /**
+     * Returns the parentId of the given nodeId.
+     *
+     * @param nodeId
+     * @return
+     * @throws RepositoryException
+     */
+    protected NodeId getParentId(NodeId nodeId) throws RepositoryException {
+        NodeId parentId;
+        if (rootID.equals(nodeId)) {
+            parentId = null; // root node reached.
+        } else {
+            parentId = getNodeById(nodeId).getParentId();
+        }
+        return parentId;
+    }
+
+    /**
+     *
+     * @param nodeId
+     * @return
+     * @throws javax.jcr.RepositoryException
+     */
+    NodeImpl getNodeById(NodeId nodeId) throws RepositoryException {
+        return ((NodeImpl) systemSession.getItemManager().getItem(nodeId));
+    }
+
+    //------------------------------------------------------------< private >---
+
+    private static NodeId accessControlledIdFromAclNode(Node aclNode) throws RepositoryException {
+        return ((NodeImpl) aclNode.getParent()).getNodeId();
+    }
+
+    private static NodeId accessControlledIdFromAceNode(Node aceNode) throws RepositoryException {
+        return ((NodeImpl) aceNode.getParent().getParent()).getNodeId();
+    }
+
+    private static void addModification(NodeId accessControllNodeId, int modType,
+                                        Map<NodeId,Integer> modMap) {
+        if (modMap.containsKey(accessControllNodeId)) {
+            // update modMap
+            modMap.put(accessControllNodeId, modType | modMap.get(accessControllNodeId));
+        } else {
+            modMap.put(accessControllNodeId, modType);
+        }
+    }
+    
+    //------------------------------------------------------< EventListener >---
+    /**
+     * Collect is of access controlled nodes that are effected by access control
+     * modification together with the corresponding modification type and
+     * finally inform listeners about the modifications.
+     * 
+     * @param events
+     */
+    public void onEvent(EventIterator events) {
+        /* map of access-controlled nodeId to type of ac modification */
+        Map<NodeId,Integer> modMap = new HashMap<NodeId,Integer>();
+
+        // collect the ids of all access controlled nodes that have been affected
+        // by the events and thus need their cache entries updated or cleared.
+        while (events.hasNext()) {
+            try {
+                Event ev = events.nextEvent();
+                String identifier = ev.getIdentifier();
+                String path = ev.getPath();
+
+                switch (ev.getType()) {
+                    case Event.NODE_ADDED:
+                        NodeImpl n = (NodeImpl) systemSession.getNodeByIdentifier(identifier);
+                        if (n.isNodeType(NT_REP_ACL)) {
+                            // a new ACL was added -> use the added node to update
+                            // the cache.
+                            addModification(accessControlledIdFromAclNode(n), POLICY_ADDED, modMap);
+                        } else if (n.isNodeType(NT_REP_ACE)) {
+                            // a new ACE was added -> use the parent node (acl)
+                            // to update the cache.
+                            addModification(accessControlledIdFromAceNode(n), POLICY_MODIFIED, modMap);
+                        } /* else: some other node added below an access controlled
+                             parent node -> not interested. */
+                        break;
+                    case Event.NODE_REMOVED:
+                        String parentPath = Text.getRelativeParent(path, 1);
+                        if (systemSession.nodeExists(parentPath)) {
+                            NodeImpl parent = (NodeImpl) systemSession.getNode(parentPath);
+                            if (repPolicyName.equals(Text.getName(path))){
+                                // the complete acl was removed -> clear cache entry
+                                addModification(parent.getNodeId(), POLICY_REMOVED, modMap);
+                            } else if (parent.isNodeType(NT_REP_ACL)) {
+                                // an ace was removed -> refresh cache for the
+                                // containing access control list upon next access
+                                addModification(accessControlledIdFromAclNode(parent), POLICY_MODIFIED, modMap);
+                            } /* else:
+                             a) some other child node of an access controlled
+                                node -> not interested.
+                             b) a child node of an ACE. not relevant for this
+                                implementation -> ignore
+                           */
+                        } else {
+                            log.debug("Cannot process NODE_REMOVED event. Parent " + parentPath + " doesn't exist (anymore).");
+                        }
+                        break;
+                    case Event.PROPERTY_CHANGED:
+                        // test if the changed prop belongs to an ACE
+                        NodeImpl parent = (NodeImpl) systemSession.getNodeByIdentifier(identifier);
+                        if (parent.isNodeType(NT_REP_ACE)) {
+                            addModification(accessControlledIdFromAceNode(parent), POLICY_MODIFIED, modMap);
+                        } /* some other property below an access controlled node
+                             changed -> not interested. (NOTE: rep:ACL doesn't
+                             define any properties. */
+                        break;
+                    default:
+                        // illegal event-type: should never occur. ignore
+                }
+            } catch (RepositoryException e) {
+                // should not get here
+                log.error("Internal error: ", e);
+            }
+        }
+
+        if (!modMap.isEmpty()) {
+            // notify listeners and eventually clean up internal caches.
+            notifyListeners(new AccessControlModifications(modMap));
+        }
+    }
+}
\ No newline at end of file

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryCollector.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java?rev=950440&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java Wed Jun  2 09:00:44 2010
@@ -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.core.security.authorization.acl;
+
+import javax.jcr.security.AccessControlEntry;
+import java.util.List;
+
+/**
+ * <code>EntryFilter</code>...
+ */
+public interface EntryFilter {
+
+    void filterEntries(List<AccessControlEntry> entries, List<AccessControlEntry>... resultLists);
+
+}
\ No newline at end of file

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilter.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java?rev=950440&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java Wed Jun  2 09:00:44 2010
@@ -0,0 +1,79 @@
+/*
+ * 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.core.security.authorization.acl;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.security.AccessControlEntry;
+import java.security.acl.Group;
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * <code>PrincipalEntryFilter</code>...
+ */
+class EntryFilterImpl implements EntryFilter {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(EntryFilterImpl.class);
+
+    private final Collection<String> principalNames;
+
+    EntryFilterImpl(Collection<String> principalNames) {
+        this.principalNames = principalNames;
+    }
+
+    /**
+     * Separately collect the entries defined for the user and group
+     * principals.
+     *
+     * @param entries
+     * @param resultLists
+     * @see EntryFilter#filterEntries(java.util.List, java.util.List[])
+     */
+    public void filterEntries(List<AccessControlEntry> entries, List<AccessControlEntry>... resultLists) {
+        if (resultLists.length == 2) {
+            List<AccessControlEntry> userAces = resultLists[0];
+            List<AccessControlEntry> groupAces = resultLists[1];
+
+            int uInsertIndex = userAces.size();
+            int gInsertIndex = groupAces.size();
+
+            // first collect aces present on the given aclNode.
+            for (AccessControlEntry ace : entries) {
+                // only process ace if 'principalName' is contained in the given set
+                if (principalNames == null || principalNames.contains(ace.getPrincipal().getName())) {
+                    // add it to the proper list (e.g. separated by principals)
+                    /**
+                     * NOTE: access control entries must be collected in reverse
+                     * order in order to assert proper evaluation.
+                     */
+                    if (ace.getPrincipal() instanceof Group) {
+                        groupAces.add(gInsertIndex, ace);
+                    } else {
+                        userAces.add(uInsertIndex, ace);
+                    }
+                }
+            }
+        } else {
+            log.warn("Filtering aborted. Expected 2 result lists.");
+        }
+    }
+}
\ No newline at end of file

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/acl/EntryFilterImpl.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

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=950440&r1=950439&r2=950440&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 Jun  2 09:00:44 2010
@@ -25,6 +25,7 @@ import org.apache.jackrabbit.core.securi
 import org.apache.jackrabbit.core.security.authorization.AbstractCompiledPermissions;
 import org.apache.jackrabbit.core.security.authorization.principalbased.ACLProvider;
 import org.apache.jackrabbit.core.ItemImpl;
+import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 import javax.jcr.security.AccessControlPolicy;
@@ -242,5 +243,13 @@ public class CombinedProvider extends Ab
             }
             super.close();
         }
+
+        /**
+         * @see CompiledPermissions#canRead(Path, ItemId)
+         */
+        public boolean canRead(Path path, ItemId itemId) throws RepositoryException {
+            Path p = (path == null) ? session.getItemManager().getItem(itemId).getPrimaryPath() : path;
+            return grants(p, Permission.READ);
+        }
     }
 }
\ No newline at end of file

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=950440&r1=950439&r2=950440&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 Jun  2 09:00:44 2010
@@ -16,49 +16,46 @@
  */
 package org.apache.jackrabbit.core.security.authorization.principalbased;
 
-import java.security.Principal;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.Item;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.PropertyType;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.jcr.Value;
-import javax.jcr.ValueFactory;
-import javax.jcr.observation.Event;
-import javax.jcr.observation.EventIterator;
-import javax.jcr.security.AccessControlEntry;
-import javax.jcr.security.AccessControlException;
-import javax.jcr.security.AccessControlManager;
-import javax.jcr.security.AccessControlPolicy;
-import javax.jcr.security.Privilege;
-
+import org.apache.commons.collections.map.LRUMap;
 import org.apache.jackrabbit.api.security.principal.PrincipalManager;
-import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
-import org.apache.jackrabbit.core.observation.SynchronousEventListener;
+import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.security.SecurityConstants;
 import org.apache.jackrabbit.core.security.authorization.AbstractAccessControlProvider;
 import org.apache.jackrabbit.core.security.authorization.AbstractCompiledPermissions;
 import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
 import org.apache.jackrabbit.core.security.authorization.AccessControlEditor;
+import org.apache.jackrabbit.core.security.authorization.AccessControlEntryImpl;
+import org.apache.jackrabbit.core.security.authorization.AccessControlListener;
+import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
 import org.apache.jackrabbit.core.security.authorization.CompiledPermissions;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.core.security.authorization.PrivilegeRegistry;
 import org.apache.jackrabbit.spi.Path;
-import org.apache.jackrabbit.spi.commons.name.PathFactoryImpl;
 import org.apache.jackrabbit.util.Text;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.Value;
+import javax.jcr.ValueFactory;
+import javax.jcr.security.AccessControlEntry;
+import javax.jcr.security.AccessControlException;
+import javax.jcr.security.AccessControlManager;
+import javax.jcr.security.AccessControlPolicy;
+import javax.jcr.security.Privilege;
+import java.security.Principal;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
 /**
  * <code>CombinedProvider</code>...
  */
@@ -69,31 +66,11 @@ public class ACLProvider extends Abstrac
     // TODO: add means to show effective-policy to a user.
     private static final AccessControlPolicy effectivePolicy = EffectivePrincipalBasedPolicy.getInstance();
 
+    private NodeImpl acRoot;    
     private ACLEditor editor;
 
-    private NodeImpl acRoot;
-
-    //-------------------------------------------------< AccessControlUtils >---
-    /**
-     * @see org.apache.jackrabbit.core.security.authorization.AccessControlUtils#isAcItem(Path)
-     */
-    public boolean isAcItem(Path absPath) throws RepositoryException {
-        Path.Element[] elems = absPath.getElements();
-        for (Path.Element elem : elems) {
-            if (N_POLICY.equals(elem.getName())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    /**
-     * @see org.apache.jackrabbit.core.security.authorization.AccessControlUtils#isAcItem(ItemImpl)
-     */
-    public boolean isAcItem(ItemImpl item) throws RepositoryException {
-        NodeImpl n = ((item.isNode()) ? (NodeImpl) item : (NodeImpl) item.getParent());
-        return n.isNodeType(NT_REP_ACL) || n.isNodeType(NT_REP_ACE);
-    }
+    private EntriesCache entriesCache;
+    private int readBits;
 
     //----------------------------------------------< AccessControlProvider >---
     /**
@@ -114,6 +91,9 @@ public class ACLProvider extends Abstrac
         }
 
         editor = new ACLEditor(session, resolver.getQPath(acRoot.getPath()));
+        entriesCache = new EntriesCache(session, editor, acRoot.getPath());
+        readBits = PrivilegeRegistry.getBits(new Privilege[] {session.getAccessControlManager().privilegeFromName(Privilege.JCR_READ)});
+
         // TODO: replace by configurable default policy (see JCR-2331)
         if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS)) {
             try {
@@ -169,6 +149,12 @@ public class ACLProvider extends Abstrac
             log.debug("... policy for principal  '"+principal.getName()+"'  already present.");
         }
     }
+    
+    @Override
+    public void close() {
+        super.close();        
+        entriesCache.close();
+    }
 
     /**
      * @see org.apache.jackrabbit.core.security.authorization.AccessControlProvider#getEffectivePolicies(Path)
@@ -181,7 +167,7 @@ public class ACLProvider extends Abstrac
            entries, returning the principal-based policy at 'absPath' (which for
            most nodes in the repository isn't available anyway) doesn't
            provide the desired information.
-           As tmp. solution some default policy is returned indicating.
+           As tmp. solution some default policy is returned instead.
            TODO: add proper evaluation and return a set of ACLs that take effect on the node at abs path
         */
         return new AccessControlPolicy[] {effectivePolicy};
@@ -215,7 +201,7 @@ public class ACLProvider extends Abstrac
         } else if (isReadOnly(principals)) {
             return getReadOnlyPermissions();
         } else {
-            return new ACLProvider.CompiledPermissionImpl(principals);
+            return new CompiledPermissionImpl(principals);
         }
     }
 
@@ -227,8 +213,8 @@ public class ACLProvider extends Abstrac
         if (isAdminOrSystem(principals)) {
             return true;
         } else {
-            CompiledPermissions cp = new CompiledPermissionImpl(principals, false);
-            return cp.grants(PathFactoryImpl.getInstance().getRootPath(), Permission.READ);
+            CompiledPermissionImpl cp = new CompiledPermissionImpl(principals, false);
+            return cp.canRead(((NodeImpl) session.getRootNode()).getPrimaryPath());
         }
     }
 
@@ -237,12 +223,17 @@ public class ACLProvider extends Abstrac
      *
      */
     private class CompiledPermissionImpl extends AbstractCompiledPermissions
-            implements SynchronousEventListener {
+            implements AccessControlListener {
 
         private final Set<Principal> principals;
         private final Set<String> acPaths;
         private List<AccessControlEntry> entries;
 
+        private boolean canReadAll;
+
+        private final Map<ItemId, Boolean> readCache = new LRUMap(2000);
+        private final Object monitor = new Object();
+
         /**
          * @param principals the underlying principals
          * @throws RepositoryException if an error occurs
@@ -260,16 +251,43 @@ public class ACLProvider extends Abstrac
 
             this.principals = principals;
             acPaths = new HashSet<String>(principals.size());
-            entries = reload();
+            reload();
 
-            // TODO: describe
             if (listenToEvents) {
-                int events = Event.PROPERTY_CHANGED | Event.PROPERTY_ADDED |
-                        Event.PROPERTY_REMOVED | Event.NODE_ADDED | Event.NODE_REMOVED;
-                String[] ntNames = new String[] {
-                        session.getJCRName(NT_REP_ACE)
-                };
-                observationMgr.addEventListener(this, events, acRoot.getPath(), true, null, ntNames, false);
+                /*
+                 Make sure this AclPermission recalculates the permissions if
+                 any ACL concerning it is modified.
+                 */
+                 entriesCache.addListener(this);
+            }
+        }
+               
+        /**
+         * @throws RepositoryException if an error occurs
+         */
+        private void reload() throws RepositoryException {
+            // reload the paths
+            acPaths.clear();
+            for (Principal p : principals) {
+                acPaths.add(editor.getPathToAcNode(p));
+            }
+
+            // and retrieve the entries from the entry-collector.
+            entries = entriesCache.getEntries(principals);
+            
+            // in addition: trivial check if read access is deny somewhere
+            // as as shortcut in #canRead(Path)
+            canReadAll = canRead(session.getQPath("/"));            
+            if (canReadAll) {
+                for (AccessControlEntry entry : entries) {
+                    AccessControlEntryImpl ace = (AccessControlEntryImpl) entry;
+                    if (!ace.isAllow() && ((ace.getPrivilegeBits() & readBits) == readBits)) {
+                        // found an ace that defines read deny for a sub tree
+                        // -> canReadAll is false.
+                        canReadAll = false;
+                        break;
+                    }
+                }
             }
         }
 
@@ -287,32 +305,22 @@ public class ACLProvider extends Abstrac
             String jcrPath = session.getJCRPath(absPath);
 
             // retrieve principal-based permissions and privileges
-            Result result;
-            if (session.itemExists(jcrPath)) {
-                Item item = session.getItem(jcrPath);
-                result = getResult(item, item.getPath(), isAcItem);
-            } else {
-                result = getResult(null, jcrPath, isAcItem);
-            }
-            return result;
+            return buildResult(jcrPath, isAcItem);
         }
 
 
         /**
          * Loop over all entries and evaluate allows/denies for those matching
          * the given jcrPath.
-         *
-         * @param target Existing target item for which the permissions will be
-         * evaluated or <code>null</code>.
+         * 
          * @param targetPath Path used for the evaluation; pointing to an
          * existing or non-existing item.
          * @param isAcItem the item
          * @return the result
          * @throws RepositoryException if an error occurs
          */
-        private Result getResult(Item target,
-                                 String targetPath,
-                                 boolean isAcItem) throws RepositoryException {
+        private Result buildResult(String targetPath,
+                                   boolean isAcItem) throws RepositoryException {
             int allows = Permission.NONE;
             int denies = Permission.NONE;
             int allowPrivileges = PrivilegeRegistry.NO_PRIVILEGE;
@@ -337,7 +345,7 @@ public class ACLProvider extends Abstrac
                     }
                 }
 
-                boolean matches = (target != null) ? entr.matches(target) : entr.matches(targetPath);
+                boolean matches = entr.matches(targetPath);
                 if (matches) {
                     if (entr.isAllow()) {
                         allowPrivileges |= Permission.diff(privs, denyPrivileges);
@@ -359,84 +367,63 @@ public class ACLProvider extends Abstrac
          */
         @Override
         public void close() {
-            try {
-                observationMgr.removeEventListener(this);
-            } catch (RepositoryException e) {
-                log.debug("Unable to unregister listener: ", e.getMessage());
-            }
+            entriesCache.removeListener(this);
             super.close();
         }
 
-        //--------------------------------------------------< EventListener >---
         /**
-         * @see javax.jcr.observation.EventListener#onEvent(EventIterator)
+         * @see CompiledPermissions#canRead(Path, ItemId)
          */
-        public synchronized void onEvent(EventIterator events) {
+        public boolean canRead(Path path, ItemId itemId) throws RepositoryException {
+            boolean canRead;
+            if (path == null) {
+                // only itemId: try to avoid expensive resolution from itemID to path
+                synchronized (monitor) {
+                    if (readCache.containsKey(itemId)) {
+                        // id has been evaluated before -> shortcut
+                        canRead = readCache.get(itemId);
+                    } else {
+                        canRead = canRead(session.getHierarchyManager().getPath(itemId));
+                        readCache.put(itemId, canRead);
+                        return canRead;
+                    }
+                }
+            } else {
+                // path param present:
+                canRead = canRead(path);
+            }
+            return canRead;
+        }
+
+        private boolean canRead(Path path) throws RepositoryException {
+            // first try if reading non-ac-items was always granted -> no eval
+            // otherwise evaluate the permissions.
+            return (canReadAll && !isAcItem(path)) || grants(path, Permission.READ);
+        }
+
+        //------------------------------------------< AccessControlListener >---
+        /**
+         * @see AccessControlListener#acModified(org.apache.jackrabbit.core.security.authorization.AccessControlModifications)
+         */
+        public void acModified(AccessControlModifications modifications) {
             try {
                 boolean reload = false;
-                while (events.hasNext() && !reload) {
-                    Event ev = events.nextEvent();
-                    String path = ev.getPath();
-                    // only invalidate cache if any of the events affects the
-                    // nodes defining permissions for the principals.
-                    switch (ev.getType()) {
-                        case Event.NODE_ADDED:
-                        case Event.NODE_REMOVED:
-                        case Event.NODE_MOVED:
-                            reload = acPaths.contains(Text.getRelativeParent(path, 2));
-                            break;
-                        case Event.PROPERTY_ADDED:
-                        case Event.PROPERTY_CHANGED:
-                        case Event.PROPERTY_REMOVED:
-                            reload = acPaths.contains(Text.getRelativeParent(path, 3));
-                            break;
-
-                        default:
-                            // illegal event-type: should never occur. ignore
-                            break;
-                    }
+                Iterator keys = modifications.getNodeIdentifiers().iterator();
+                while (keys.hasNext() && !reload) {
+                    String path = keys.next().toString();
+                    reload = acPaths.contains(path);
                 }
                 // eventually reload the ACL and clear the cache
                 if (reload) {
                     clearCache();
-                    // reload the acl
-                    entries = reload();
+                    // reload the ac-path list and the list of aces
+                    reload();
                 }
             } catch (RepositoryException e) {
                 // should never get here
                 log.warn("Internal error: ", e.getMessage());
             }
         }
-
-        /**
-         *
-         * @return the aces
-         * @throws RepositoryException if an error occurs
-         */
-        private List<AccessControlEntry> reload() throws RepositoryException {
-            // reload the paths
-            acPaths.clear();
-
-            // acNodes must be ordered in the same order as the principals
-            // in order to obtain proper acl-evaluation in case the given
-            // principal-set is ordered.
-            List<AccessControlEntry> allACEs = new ArrayList<AccessControlEntry>();
-            // build acl-hierarchy assuming that principal-order determines the
-            // acl-inheritance.
-            for (Principal p : principals) {
-                ACLTemplate acl = editor.getACL(p);
-                if (acl == null || acl.isEmpty()) {
-                    acPaths.add(editor.getPathToAcNode(p));
-                } else {
-                    // retrieve the ACEs from the node
-                    AccessControlEntry[] aces = acl.getAccessControlEntries();
-                    allACEs.addAll(Arrays.asList(aces));
-                    acPaths.add(acl.getPath());
-                }
-            }
-
-            return allACEs;
-        }
     }
 
     //--------------------------------------------------------------------------

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java?rev=950440&r1=950439&r2=950440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/ACLTemplate.java Wed Jun  2 09:00:44 2010
@@ -155,6 +155,7 @@ class ACLTemplate extends AbstractACLTem
     /**
      * @see AbstractACLTemplate#checkValidEntry(java.security.Principal, javax.jcr.security.Privilege[], boolean, java.util.Map)
      */
+    @Override
     protected void checkValidEntry(Principal principal, Privilege[] privileges,
                                  boolean isAllow, Map<String, Value> restrictions)
             throws AccessControlException {
@@ -171,6 +172,7 @@ class ACLTemplate extends AbstractACLTem
     /**
      * @see org.apache.jackrabbit.core.security.authorization.AbstractACLTemplate#getEntries() 
      */
+    @Override
     protected List<? extends AccessControlEntry> getEntries() {
         return entries;
     }
@@ -197,20 +199,6 @@ class ACLTemplate extends AbstractACLTem
     }
 
     /**
-     * @see JackrabbitAccessControlList#isEmpty()
-     */
-    public boolean isEmpty() {
-        return entries.isEmpty();
-    }
-
-    /**
-     * @see org.apache.jackrabbit.api.security.JackrabbitAccessControlList#size()
-     */
-    public int size() {
-        return entries.size();
-    }
-
-    /**
      * Known restrictions are:
      * <pre>
      *   rep:nodePath  (mandatory) value-type: PATH
@@ -241,14 +229,6 @@ class ACLTemplate extends AbstractACLTem
 
     //--------------------------------------------------< AccessControlList >---
     /**
-     * @see javax.jcr.security.AccessControlList#getAccessControlEntries()
-     */
-    public AccessControlEntry[] getAccessControlEntries()
-            throws RepositoryException {
-        return entries.toArray(new AccessControlEntry[entries.size()]);
-    }
-
-    /**
      * @see javax.jcr.security.AccessControlList#removeAccessControlEntry(AccessControlEntry)
      */
     public void removeAccessControlEntry(AccessControlEntry ace)
@@ -269,6 +249,7 @@ class ACLTemplate extends AbstractACLTem
      * @return always zero
      * @see Object#hashCode()
      */
+    @Override
     public int hashCode() {
         return 0;
     }
@@ -280,6 +261,7 @@ class ACLTemplate extends AbstractACLTem
      * @return true if the path and the entries are equal; false otherwise.
      * @see Object#equals(Object)
      */
+    @Override
     public boolean equals(Object obj) {
         if (obj == this) {
             return true;
@@ -305,7 +287,7 @@ class ACLTemplate extends AbstractACLTem
         private final String nodePath;
 
         /**
-         * Globbing pattern
+         * Globing pattern
          */
         private final GlobPattern pattern;
 

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java?rev=950440&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java Wed Jun  2 09:00:44 2010
@@ -0,0 +1,225 @@
+/*
+ * 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.core.security.authorization.principalbased;
+
+import org.apache.commons.collections.map.LRUMap;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.security.authorization.AccessControlConstants;
+import org.apache.jackrabbit.core.security.authorization.AccessControlModifications;
+import org.apache.jackrabbit.core.security.authorization.AccessControlObserver;
+import org.apache.jackrabbit.util.Text;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.ObservationManager;
+import javax.jcr.security.AccessControlEntry;
+import java.security.Principal;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <code>CachingEntryCollector</code>...
+ */
+class EntriesCache extends AccessControlObserver implements AccessControlConstants {
+
+    /**
+     * logger instance
+     */
+    private static final Logger log = LoggerFactory.getLogger(EntriesCache.class);
+
+    private final SessionImpl systemSession;
+    private final ACLEditor systemEditor;
+
+    private final String repPolicyName;
+
+    /**
+     * Cache to look up the list of access control entries defined for a given
+     * set of principals.
+     */
+    private final Map<Object, List<AccessControlEntry>> cache;
+    private final Object monitor = new Object();
+
+    /**
+     *
+     * @param systemSession
+     * @param systemEditor
+     * @param accessControlRootPath
+     * @throws javax.jcr.RepositoryException
+     */
+    EntriesCache(SessionImpl systemSession, ACLEditor systemEditor,
+                          String accessControlRootPath) throws RepositoryException {
+        this.systemSession = systemSession;
+        this.systemEditor = systemEditor;
+
+        repPolicyName = systemSession.getJCRName(N_POLICY);
+
+        cache = new LRUMap(1000);
+
+        ObservationManager observationMgr = systemSession.getWorkspace().getObservationManager();
+        /*
+         Make sure the collector and all subscribed listeners are informed upon
+         ACL modifications. Interesting events are:
+         - new ACL (NODE_ADDED)
+         - new ACE (NODE_ADDED)
+         - changing ACE (PROPERTY_CHANGED)
+         - removed ACL (NODE_REMOVED)
+         - removed ACE (NODE_REMOVED)
+        */
+        int events = Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED;
+        String[] ntNames = new String[] {
+                systemSession.getJCRName(NT_REP_ACCESS_CONTROLLABLE),
+                systemSession.getJCRName(NT_REP_ACL),
+                systemSession.getJCRName(NT_REP_ACE)
+        };
+        observationMgr.addEventListener(this, events, accessControlRootPath, true, null, ntNames, false);
+    }
+
+    /**
+     * Release all resources contained by this instance. It will no longer be
+     * used. This implementation only stops listening to ac modification events.
+     */
+    protected void close() {
+        super.close();
+        try {
+            systemSession.getWorkspace().getObservationManager().removeEventListener(this);
+        } catch (RepositoryException e) {
+            log.error("Unexpected error while closing CachingEntryCollector", e);
+        }
+    }
+
+    List<AccessControlEntry> getEntries(Collection<Principal> principals) throws RepositoryException {
+        Object key = getCacheKey(principals);
+        List<AccessControlEntry> entries;
+        synchronized (monitor) {
+            entries = cache.get(key);
+            if (entries == null) {
+                // acNodes must be ordered in the same order as the principals
+                // in order to obtain proper acl-evaluation in case the given
+                // principal-set is ordered.
+                entries = new ArrayList<AccessControlEntry>();
+                // build acl-hierarchy assuming that principal-order determines
+                // the acl-inheritance.
+                for (Principal p : principals) {
+                    ACLTemplate acl = systemEditor.getACL(p);
+                    if (acl != null && !acl.isEmpty()) {
+                        AccessControlEntry[] aces = acl.getAccessControlEntries();
+                        entries.addAll(Arrays.asList(aces));
+                    }
+                }
+                cache.put(key, entries);
+            }
+        }
+        return entries;
+    }
+
+    private static Object getCacheKey(Collection<Principal> principals) {
+        StringBuilder sb = new StringBuilder();
+        for (Principal p : principals) {
+            sb.append(p.getName()).append('/');
+        }
+        return sb.toString();
+    }
+
+    //------------------------------------------------------< EventListener >---
+    /**
+     * @see javax.jcr.observation.EventListener#onEvent(javax.jcr.observation.EventIterator)
+     */
+    public synchronized void onEvent(EventIterator events) {
+        /* map of path-string to modification type.
+           the path identifies the access-controlled node for a given principal.*/
+        Map<String,Integer> modMap = new HashMap<String,Integer>();
+
+        // collect the paths of all access controlled nodes that have been affected
+        // by the events and thus need their cache entries updated or cleared.
+        while (events.hasNext()) {
+            try {
+                Event ev = events.nextEvent();
+                String identifier = ev.getIdentifier();
+                String path = ev.getPath();
+
+                switch (ev.getType()) {
+                    case Event.NODE_ADDED:
+                        NodeImpl n = (NodeImpl) systemSession.getNodeByIdentifier(identifier);
+                        if (n.isNodeType(NT_REP_ACL)) {
+                            // a new ACL was added -> use the added node to update
+                            // the cache.
+                            modMap.put(Text.getRelativeParent(path, 1), POLICY_ADDED);
+                        } else if (n.isNodeType(NT_REP_ACE)) {
+                            // a new ACE was added -> use the parent node (acl)
+                            // to update the cache.
+                            modMap.put(Text.getRelativeParent(path, 2), POLICY_MODIFIED);
+                        } /* else: some other node added below an access controlled
+                             parent node -> not interested. */
+                        break;
+                    case Event.NODE_REMOVED:
+                        String parentPath = Text.getRelativeParent(path, 1);
+                        if (systemSession.nodeExists(parentPath)) {
+                            NodeImpl parent = (NodeImpl) systemSession.getNode(parentPath);
+                            if (repPolicyName.equals(Text.getName(path))){
+                                // the complete acl was removed -> clear cache entry
+                                modMap.put(parentPath, POLICY_REMOVED);
+                            } else if (parent.isNodeType(NT_REP_ACL)) {
+                                // an ace was removed -> refresh cache for the
+                                // containing access control list upon next access
+                                modMap.put(Text.getRelativeParent(path, 2), POLICY_MODIFIED);
+                            } /* else:
+                             a) some other child node of an access controlled
+                                node -> not interested.
+                             b) a child node of an ACE. not relevant for this
+                                implementation -> ignore
+                           */
+                        } else {
+                            log.debug("Cannot process NODE_REMOVED event. Parent " + parentPath + " doesn't exist (anymore).");
+                        }
+                        break;
+                    case Event.PROPERTY_CHANGED:
+                        // test if the changed prop belongs to an ACE
+                        NodeImpl parent = (NodeImpl) systemSession.getNodeByIdentifier(identifier);
+                        if (parent.isNodeType(NT_REP_ACE)) {
+                            modMap.put(Text.getRelativeParent(path, 3), POLICY_MODIFIED);
+                        } /* some other property below an access controlled node
+                             changed -> not interested. (NOTE: rep:ACL doesn't
+                             define any properties. */
+                        break;
+                    default:
+                        // illegal event-type: should never occur. ignore
+                }
+
+            } catch (RepositoryException e) {
+                // should never get here
+                log.warn("Internal error: ", e.getMessage());
+            }
+
+        }
+
+        if (!modMap.isEmpty()) {
+            // notify listeners and eventually clean up internal caches.
+            synchronized (monitor) {
+                cache.clear();
+            }
+            notifyListeners(new AccessControlModifications(modMap));
+        }
+    }
+}

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

Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/EntriesCache.java
------------------------------------------------------------------------------
    svn:keywords = Author Date Id Revision Rev URL

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java?rev=950440&r1=950439&r2=950440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/authorization/principalbased/GlobPattern.java Wed Jun  2 09:00:44 2010
@@ -36,9 +36,19 @@ class GlobPattern {
     public static final String WILDCARD_ALL = "*";
 
     private final String pattern;
+    private final char[] patternChars;
+
+    private final boolean matchesAll;
+    private final boolean containsWildcard;
 
     private GlobPattern(String pattern)  {
         this.pattern = pattern;
+
+        matchesAll = WILDCARD_ALL.equals(pattern);
+        containsWildcard = pattern.indexOf(ALL) > -1;
+
+        patternChars = pattern.toCharArray();
+
     }
 
     static GlobPattern create(String pattern) {
@@ -50,17 +60,17 @@ class GlobPattern {
 
     boolean matches(String toMatch) {
         // shortcut
-        if (WILDCARD_ALL.equals(pattern)) {
+        if (matchesAll) {
             return true;
         }
         if (toMatch == null) {
             return false;
         }
 
-        if (containsWildCard()) {
-            return matches(pattern, toMatch);
+        if (containsWildcard) {
+            return matches(patternChars, toMatch.toCharArray());
         } else {
-            return pattern.equals(toMatch) || Text.isDescendant(pattern, toMatch);
+            return Text.isDescendantOrEqual(pattern, toMatch);
         }
     }
 
@@ -74,21 +84,13 @@ class GlobPattern {
         }
     }
 
-    private boolean containsWildCard() {
+    private static boolean matches(char[] patternChars, char[] toMatch) {
         // TODO: add proper impl
-        return pattern.indexOf(ALL) > -1;
-    }
-
-    private static boolean matches(String pattern, String toMatch) {
-        // TODO: add proper impl
-        char[] c1 = pattern.toCharArray();
-        char[] c2 = toMatch.toCharArray();
-
-        for (int i = 0; i < c1.length; i++) {
-            if (c1[i] == ALL) {
+        for (int i = 0; i < patternChars.length; i++) {
+            if (patternChars[i] == ALL) {
                 return true;
             }
-            if (i >= c2.length || c1[i] != c2[i]) {
+            if (i >= toMatch.length || patternChars[i] != toMatch[i]) {
                 return false;
             }
         }
@@ -101,6 +103,7 @@ class GlobPattern {
     /**
      * @see Object#hashCode()
      */
+    @Override
     public int hashCode() {
         return pattern.hashCode();
     }
@@ -108,6 +111,7 @@ class GlobPattern {
     /**
      * @see Object#toString()
      */
+    @Override
     public String toString() {
         return pattern;
     }
@@ -115,6 +119,7 @@ class GlobPattern {
     /**
      * @see Object#equals(Object)
      */
+    @Override
     public boolean equals(Object obj) {
         if (obj == this) {
             return true;

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleAccessManager.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleAccessManager.java?rev=950440&r1=950439&r2=950440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleAccessManager.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleAccessManager.java Wed Jun  2 09:00:44 2010
@@ -167,7 +167,7 @@ public class SimpleAccessManager extends
         return internalIsGranted(parentPath, permissions);
     }
 
-    public boolean canRead(Path itemPath) throws RepositoryException {
+    public boolean canRead(Path itemPath, ItemId itemId) throws RepositoryException {
         return true;
     }
 

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleLoginModule.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleLoginModule.java?rev=950440&r1=950439&r2=950440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleLoginModule.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/security/simple/SimpleLoginModule.java Wed Jun  2 09:00:44 2010
@@ -41,6 +41,7 @@ public class SimpleLoginModule extends A
     /**
      * @see AbstractLoginModule#doInit(javax.security.auth.callback.CallbackHandler, javax.jcr.Session, java.util.Map)
      */
+    @Override
     protected void doInit(CallbackHandler callbackHandler, Session session, Map options) throws LoginException {
         // nothing to do
         log.debug("init: SimpleLoginModule. Done.");
@@ -49,6 +50,7 @@ public class SimpleLoginModule extends A
     /**
      * @see AbstractLoginModule#impersonate(java.security.Principal, javax.jcr.Credentials)
      */
+    @Override
     protected boolean impersonate(Principal principal, Credentials credentials) throws RepositoryException, LoginException {
         if (principal instanceof Group) {
             return false;
@@ -60,6 +62,7 @@ public class SimpleLoginModule extends A
     /**
      * @see AbstractLoginModule#getAuthentication(java.security.Principal, javax.jcr.Credentials)
      */
+    @Override
     protected Authentication getAuthentication(Principal principal, Credentials creds) throws RepositoryException {
         if (principal instanceof Group) {
             return null;
@@ -86,6 +89,7 @@ public class SimpleLoginModule extends A
      *
      * @see AbstractLoginModule#getPrincipal(Credentials)
      */
+    @Override
     protected Principal getPrincipal(Credentials credentials) {
         String userId = getUserID(credentials);
         Principal principal = principalProvider.getPrincipal(userId);

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=950440&r1=950439&r2=950440&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 Jun  2 09:00:44 2010
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.api.securit
 import org.apache.jackrabbit.core.ItemImpl;
 import org.apache.jackrabbit.core.NodeImpl;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.id.ItemId;
 import org.apache.jackrabbit.core.observation.SynchronousEventListener;
 import org.apache.jackrabbit.core.security.authorization.AbstractAccessControlProvider;
 import org.apache.jackrabbit.core.security.authorization.AbstractCompiledPermissions;
@@ -462,6 +463,13 @@ public class UserAccessControlProvider e
             return session.nodeExists(userNodePath);
         }
 
+        /**
+         * @see CompiledPermissions#canRead(Path, ItemId)
+         */
+        public boolean canRead(Path path, ItemId itemId) throws RepositoryException {
+            return canReadAll();
+        }
+
         //--------------------------------------------------< EventListener >---
         /**
          * Event listener is only interested in changes of group-membership

Modified: jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/AccessManagerTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/AccessManagerTest.java?rev=950440&r1=950439&r2=950440&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/AccessManagerTest.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/test/java/org/apache/jackrabbit/core/security/AccessManagerTest.java Wed Jun  2 09:00:44 2010
@@ -21,6 +21,8 @@ import org.apache.jackrabbit.core.ItemIm
 import org.apache.jackrabbit.core.id.NodeId;
 import org.apache.jackrabbit.core.id.PropertyId;
 import org.apache.jackrabbit.core.SessionImpl;
+import org.apache.jackrabbit.core.NodeImpl;
+import org.apache.jackrabbit.core.PropertyImpl;
 import org.apache.jackrabbit.core.security.authorization.Permission;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.name.NameConstants;
@@ -280,4 +282,51 @@ public class AccessManagerTest extends A
             s.logout();
         }
     }
+
+    public void testCanReadPathId() throws Exception {
+        Session s = getHelper().getReadOnlySession();
+        try {
+            AccessManager acMgr = getAccessManager(s);
+
+            ItemId id = ((NodeImpl) testRootNode).getId();
+            Path path = ((NodeImpl) testRootNode).getPrimaryPath();
+            assertTrue(acMgr.canRead(null, id));
+            assertTrue(acMgr.canRead(path, null));
+            assertTrue(acMgr.canRead(path, id));
+
+            id = ((PropertyImpl) testRootNode.getProperty(jcrPrimaryType)).getId();
+            path = ((PropertyImpl) testRootNode.getProperty(jcrPrimaryType)).getPrimaryPath();
+            assertTrue(acMgr.canRead(null, id));
+            assertTrue(acMgr.canRead(path, null));
+            assertTrue(acMgr.canRead(path, id));
+
+        } finally {
+            s.logout();
+        }
+
+    }
+
+    public void testCanReadNewId() throws Exception {
+        Session s = getHelper().getReadOnlySession();
+        try {
+            NodeImpl n = (NodeImpl) testRootNode.addNode(nodeName1);
+            PropertyImpl p = (PropertyImpl) n.setProperty(propertyName1, "somevalue");
+            try {
+                AccessManager acMgr = getAccessManager(s);
+                acMgr.canRead(null, n.getId());
+                fail("expected repositoryexception");
+            } catch (RepositoryException e) {
+                // success
+            }
+            try {
+                AccessManager acMgr = getAccessManager(s);
+                acMgr.canRead(null, p.getId());
+                fail("expected repositoryexception");
+            } catch (RepositoryException e) {
+                // success
+            }
+        } finally {
+            s.logout();
+        }
+    }
 }
\ No newline at end of file



Mime
View raw message