Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/ConcurrentCreateUserTest.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/ConcurrentCreateUserTest.java (revision 0) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/ConcurrentCreateUserTest.java (revision 0) @@ -0,0 +1,109 @@ +/* + * 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.api.security.user; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.UUID; + +import javax.jcr.InvalidItemStateException; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.core.AbstractConcurrencyTest; +import org.apache.jackrabbit.core.security.TestPrincipal; + +public class ConcurrentCreateUserTest extends AbstractConcurrencyTest { + + /** + * The number of threads. + */ + private static final int CONCURRENCY = 5; + + /** + * Number of tries per user. + */ + private static final int RETRIES = 3; + + List userIDs = Collections + .synchronizedList(new ArrayList()); + public static final String INTERMEDIATE_PATH = UUID.randomUUID().toString(); + + @Override + protected void tearDown() throws Exception { + + try { + for (String id : userIDs) { + Authorizable a = ((JackrabbitSession) superuser) + .getUserManager().getAuthorizable(id); + a.remove(); + superuser.save(); + } + } catch (Exception e) { + // this is best effort + } + + super.tearDown(); + } + + public void testCreateUsers() throws Exception { + + log.println("ConcurrentCreateUserTest.testCreateUsers: c=" + + CONCURRENCY); + log.flush(); + + runTask(new Task() { + public void execute(Session session, Node test) + throws RepositoryException { + JackrabbitSession s = null; + try { + s = (JackrabbitSession) getHelper().getSuperuserSession(); + + String name = "newname-" + UUID.randomUUID(); + Authorizable authorizable = null; + int maxtries = RETRIES; + RepositoryException lastex = null; + + while (authorizable == null && maxtries > 0) { + try { + maxtries -= 1; + authorizable = s.getUserManager().createUser(name, + "password1", new TestPrincipal(name), + INTERMEDIATE_PATH); + s.save(); + } catch (InvalidItemStateException ex) { + lastex = ex; + log.println("got " + ex + ", retrying"); + } + } + if (authorizable == null) { + throw new RepositoryException("user " + name + + " not created in " + RETRIES + " attempts.", + lastex); + } + userIDs.add(authorizable.getID()); + log.println(authorizable + " created"); + } finally { + s.logout(); + } + } + }, CONCURRENCY, "/" + testPath); + } +} Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java =================================================================== --- jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java (revision 1177306) +++ jackrabbit-core/src/test/java/org/apache/jackrabbit/api/security/user/TestAll.java (working copy) @@ -42,6 +42,7 @@ suite.addTestSuite(NestedGroupTest.class); suite.addTestSuite(ImpersonationTest.class); suite.addTestSuite(UserManagerSearchTest.class); + suite.addTestSuite(ConcurrentCreateUserTest.class); return suite; } Index: jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java =================================================================== --- jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java (revision 1177306) +++ jackrabbit-api/src/main/java/org/apache/jackrabbit/api/security/user/UserManager.java (working copy) @@ -16,16 +16,24 @@ */ package org.apache.jackrabbit.api.security.user; -import javax.jcr.RepositoryException; -import javax.jcr.UnsupportedRepositoryOperationException; import java.security.Principal; import java.util.Iterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.UnsupportedRepositoryOperationException; + /** * The UserManager provides access to and means to maintain * {@link Authorizable authorizable objects} i.e. {@link User users} and * {@link Group groups}. The UserManager is bound to a particular * Session. + *

+ * Note that all create calls will modify the current session. If + * the user manager is not in "autosave" mode (see + * {@link UserManager#isAutoSave()}), problems like overlapping creation of + * intermediate nodes may only surface upon a subsequent {@link Session#save()} + * operation; callers should be prepared to repeat them in case this happens. */ public interface UserManager {