Index: src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java (revision 1518513) +++ src/main/java/org/apache/jackrabbit/core/DefaultSecurityManager.java (working copy) @@ -49,6 +49,7 @@ import org.apache.jackrabbit.core.security.DefaultAccessManager; import org.apache.jackrabbit.core.security.JackrabbitSecurityManager; import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.SystemPrincipal; import org.apache.jackrabbit.core.security.authentication.AuthContext; import org.apache.jackrabbit.core.security.authentication.AuthContextProvider; import org.apache.jackrabbit.core.security.authorization.AccessControlProvider; @@ -336,11 +337,15 @@ public String getUserID(Subject subject, String workspaceName) throws RepositoryException { checkInitialized(); - /* shortcut if the subject contains the AdminPrincipal in which case - the userID is already known. */ + // shortcut if the subject contains the AdminPrincipal or + // SystemPrincipal in which cases the userID is already known. if (!subject.getPrincipals(AdminPrincipal.class).isEmpty()) { return adminId; + } else if (!subject.getPrincipals(SystemPrincipal.class).isEmpty()) { + // system session does not have a userId + return null; } + /* if there is a configure principal class that should be used to determine the UserID -> try this one. */ Class cl = getConfig().getUserIdClass(); Index: src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java =================================================================== --- src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java (revision 1518513) +++ src/main/java/org/apache/jackrabbit/core/security/user/MembershipCache.java (working copy) @@ -19,7 +19,6 @@ import java.util.Collection; import java.util.Collections; import java.util.HashSet; -import java.util.Map; import java.util.Set; import javax.jcr.AccessDeniedException; @@ -39,7 +38,7 @@ import org.apache.jackrabbit.core.PropertyImpl; import org.apache.jackrabbit.core.SessionImpl; import org.apache.jackrabbit.core.SessionListener; -import org.apache.jackrabbit.core.cache.GrowingLRUMap; +import org.apache.jackrabbit.core.cache.ConcurrentCache; import org.apache.jackrabbit.core.nodetype.NodeTypeImpl; import org.apache.jackrabbit.core.observation.SynchronousEventListener; import org.apache.jackrabbit.spi.Name; @@ -58,11 +57,6 @@ private static final Logger log = LoggerFactory.getLogger(MembershipCache.class); /** - * The initial size of this cache (TODO: make configurable) - */ - private static final int MAX_INITIAL_CACHE_SIZE = 1024; - - /** * The maximum size of this cache (TODO: make configurable) */ private static final int MAX_CACHE_SIZE = 5000; @@ -71,7 +65,7 @@ private final String groupsPath; private final boolean useMembersNode; private final String pMembers; - private final Map> cache; + private final ConcurrentCache> cache; @SuppressWarnings("unchecked") MembershipCache(SessionImpl systemSession, String groupsPath, boolean useMembersNode) throws RepositoryException { @@ -80,7 +74,8 @@ this.useMembersNode = useMembersNode; pMembers = systemSession.getJCRName(UserManagerImpl.P_MEMBERS); - cache = new GrowingLRUMap(MAX_INITIAL_CACHE_SIZE, MAX_CACHE_SIZE); + cache = new ConcurrentCache>("MembershipCache", 16); + cache.setMaxMemorySize(MAX_CACHE_SIZE); String[] ntNames = new String[] { systemSession.getJCRName(UserConstants.NT_REP_GROUP), @@ -143,9 +138,7 @@ } if (clear) { - synchronized (cache) { - cache.clear(); - } + cache.clear(); log.debug("Membership cache cleared because of observation event."); } } @@ -178,7 +171,7 @@ * authorizable in question is declared member of. * @throws RepositoryException If an error occurs. */ - synchronized Collection getDeclaredMemberOf(String authorizableNodeIdentifier) throws RepositoryException { + Collection getDeclaredMemberOf(String authorizableNodeIdentifier) throws RepositoryException { return declaredMemberOf(authorizableNodeIdentifier); } @@ -189,7 +182,7 @@ * authorizable in question is a direct or indirect member of. * @throws RepositoryException If an error occurs. */ - synchronized Collection getMemberOf(String authorizableNodeIdentifier) throws RepositoryException { + Collection getMemberOf(String authorizableNodeIdentifier) throws RepositoryException { Set groupNodeIds = new HashSet(); memberOf(authorizableNodeIdentifier, groupNodeIds); return Collections.unmodifiableCollection(groupNodeIds); @@ -199,11 +192,18 @@ * Returns the size of the membership cache * @return the size */ - synchronized int getSize() { - return cache.size(); + int getSize() { + return (int) cache.getElementCount(); } /** + * For testing purposes only. + */ + void clear() { + cache.clear(); + } + + /** * Collects the declared memberships for the specified identifier of an * authorizable using the specified session. * @@ -282,7 +282,7 @@ Session session = getSession(); try { groupNodeIds = collectDeclaredMembership(authorizableNodeIdentifier, session); - cache.put(authorizableNodeIdentifier, Collections.unmodifiableCollection(groupNodeIds)); + cache.put(authorizableNodeIdentifier, Collections.unmodifiableCollection(groupNodeIds), 1); } finally { // release session if it isn't the original system session @@ -299,7 +299,7 @@ groupNodeIds.size(), authorizableNodeIdentifier, (t1-t0) / 1000, - cache.size() + cache.getElementCount() }); } return groupNodeIds; Index: src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java =================================================================== --- src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java (revision 0) +++ src/test/java/org/apache/jackrabbit/core/security/user/MembershipCacheTest.java (revision 0) @@ -0,0 +1,235 @@ +/* + * 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.user; + +import java.io.File; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Random; +import java.util.concurrent.atomic.AtomicLong; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.SimpleCredentials; + +import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.test.JUnitTest; + +/** + * MembershipCacheTest... + */ +public class MembershipCacheTest extends JUnitTest { + + private static final String TEST_USER_PREFIX = "MembershipCacheTestUser-"; + private static final String REPO_HOME = new File("target", + MembershipCacheTest.class.getSimpleName()).getPath(); + private static final int NUM_USERS = 100; + private static final int NUM_GROUPS = 8; + private static final int NUM_READERS = 8; + private RepositoryImpl repo; + private JackrabbitSession session; + private UserManager userMgr; + private MembershipCache cache; + + @Override + protected void setUp() throws Exception { + super.setUp(); + FileUtils.deleteDirectory(new File(REPO_HOME)); + RepositoryConfig config = RepositoryConfig.create( + getClass().getResourceAsStream("repository.xml"), REPO_HOME); + repo = RepositoryImpl.create(config); + session = createSession(); + userMgr = session.getUserManager(); + cache = ((UserManagerImpl) userMgr).getMembershipCache(); + boolean autoSave = userMgr.isAutoSave(); + userMgr.autoSave(false); + // create test users and groups + List users = new ArrayList(); + for (int i = 0; i < NUM_USERS; i++) { + users.add(userMgr.createUser(TEST_USER_PREFIX + i, "secret")); + } + for (int i = 0; i < NUM_GROUPS; i++) { + Group g = userMgr.createGroup("MembershipCacheTestGroup-" + i); + for (User u : users) { + g.addMember(u); + } + } + session.save(); + userMgr.autoSave(autoSave); + logger.info("Initial cache size: " + cache.getSize()); + } + + @Override + protected void tearDown() throws Exception { + boolean autoSave = userMgr.isAutoSave(); + userMgr.autoSave(false); + for (int i = 0; i < NUM_USERS; i++) { + userMgr.getAuthorizable(TEST_USER_PREFIX + i).remove(); + } + for (int i = 0; i < NUM_GROUPS; i++) { + userMgr.getAuthorizable("MembershipCacheTestGroup-" + i).remove(); + } + session.save(); + userMgr.autoSave(autoSave); + userMgr = null; + cache = null; + session.logout(); + repo.shutdown(); + repo = null; + FileUtils.deleteDirectory(new File(REPO_HOME)); + super.tearDown(); + } + + public void testConcurrency() throws Exception { + Stats stats = new Stats(); + List exceptions = Collections.synchronizedList(new ArrayList()); + List readers = new ArrayList(); + for (int i = 0; i < NUM_READERS; i++) { + Reader r = new Reader(createSession(), stats, exceptions); + r.addUser(TEST_USER_PREFIX + 0); + readers.add(r); + } + Node test = session.getRootNode().addNode("test", "nt:unstructured"); + session.save(); + for (Reader r : readers) { + r.start(); + } + for (int i = 1; i < NUM_USERS; i++) { + test.addNode("node-" + i); + session.save(); + for (Reader r : readers) { + r.addUser(TEST_USER_PREFIX + i); + } + } + for (Reader r : readers) { + r.join(); + } + test.remove(); + session.save(); + System.out.println(stats); + for (Exception e : exceptions) { + throw e; + } + } + + public void testRun75() throws Exception { + for (int i = 0; i < 75; i++) { + testConcurrency(); + cache.clear(); + } + } + + private JackrabbitSession createSession() throws RepositoryException { + return (JackrabbitSession) repo.login( + new SimpleCredentials("admin", "admin".toCharArray())); + } + + private static final class Reader extends Thread { + + private final JackrabbitSession session; + private final UserManager userMgr; + private final Stats stats; + private final List knownUsers = new ArrayList(); + private final Random random = new Random(); + private final List exceptions; + + public Reader(JackrabbitSession s, + Stats stats, + List exceptions) + throws RepositoryException { + this.session = s; + this.userMgr = s.getUserManager(); + this.stats = stats; + this.exceptions = exceptions; + } + + void addUser(String user) { + synchronized (knownUsers) { + knownUsers.add(user); + } + } + + public void run() { + try { + while (knownUsers.size() < NUM_USERS) { + Object idOrUser; + int idx; + synchronized (knownUsers) { + idx = random.nextInt(knownUsers.size()); + idOrUser = knownUsers.get(idx); + } + User user; + if (idOrUser instanceof String) { + user = (User) userMgr.getAuthorizable((String) idOrUser); + synchronized (knownUsers) { + knownUsers.set(idx, user); + } + } else { + user = (User) idOrUser; + } + long time = System.nanoTime(); + user.memberOf(); + stats.logTime(System.nanoTime() - time); + } + } catch (RepositoryException e) { + exceptions.add(e); + } finally { + session.logout(); + } + } + } + + private static final class Stats { + + private AtomicLong[] buckets = new AtomicLong[20]; + + public Stats() { + for (int i = 0; i < buckets.length; i++) { + buckets[i] = new AtomicLong(); + } + } + + void logTime(long nanos) { + if (nanos == 0) { + buckets[0].incrementAndGet(); + } else { + buckets[(int) Math.log10(nanos)].incrementAndGet(); + } + } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + String separator = ""; + for (AtomicLong bucket : buckets) { + sb.append(separator); + sb.append(bucket.get()); + separator = ","; + } + return sb.toString(); + } + } + + +} Property changes on: src\test\java\org\apache\jackrabbit\core\security\user\MembershipCacheTest.java ___________________________________________________________________ Added: svn:keywords + Author Date Id Revision Rev URL Added: svn:eol-style + native Index: src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml =================================================================== --- src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml (revision 0) +++ src/test/resources/org/apache/jackrabbit/core/security/user/repository.xml (revision 0) @@ -0,0 +1,90 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Property changes on: src\test\resources\org\apache\jackrabbit\core\security\user\repository.xml ___________________________________________________________________ Added: svn:eol-style + native