/* * 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; /** * Performance test for JCR-3658. */ 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<User> users = new ArrayList<User>(); 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<Exception> exceptions = Collections.synchronizedList(new ArrayList<Exception>()); List<Reader> readers = new ArrayList<Reader>(); 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<Object> knownUsers = new ArrayList<Object>(); private final Random random = new Random(); private final List<Exception> exceptions; public Reader(JackrabbitSession s, Stats stats, List<Exception> 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(); } } }