Index: modeshape-jcr/pom.xml =================================================================== --- modeshape-jcr/pom.xml (revision 2009) +++ modeshape-jcr/pom.xml (working copy) @@ -63,6 +63,16 @@ Testing (note the scope) --> + org.modeshape + modeshape-search-lucene + test + + + org.modeshape + modeshape-clustering + test + + junit junit Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrEngine.java (working copy) @@ -91,7 +91,7 @@ public class JcrEngine extends ModeShapeEngine implements Repositories { /** * Clean up session-scoped locks created by session that are no longer active by iterating over the {@link JcrRepository - * repositories} and calling their {@link JcrRepository#cleanUpLocks() clean-up method}. + * repositories} and calling their {@link RepositoryLockManager#cleanUpLocks() clean-up method}. *

* It should not be possible for a session to be terminated without cleaning up its locks, but this method will help clean-up * dangling locks should a session terminate abnormally. @@ -110,7 +110,7 @@ public class JcrEngine extends ModeShapeEngine implements Repositories { for (JcrRepository repository : repos) { try { - repository.cleanUpLocks(); + repository.getRepositoryLockManager().cleanUpLocks(); } catch (Throwable t) { log.error(t, JcrI18n.errorCleaningUpLocks, repository.getRepositorySourceName()); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -30,6 +30,8 @@ import java.security.AccessControlContext; import java.security.AccessControlException; import java.security.AccessController; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.EnumMap; import java.util.HashMap; @@ -40,8 +42,6 @@ import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.WeakHashMap; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -57,7 +57,6 @@ import javax.jcr.query.Query; import javax.security.auth.Subject; import javax.security.auth.login.Configuration; import javax.security.auth.login.LoginContext; -import net.jcip.annotations.GuardedBy; import net.jcip.annotations.Immutable; import net.jcip.annotations.ThreadSafe; import org.modeshape.common.collection.UnmodifiableProperties; @@ -69,8 +68,6 @@ import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.Graph; import org.modeshape.graph.GraphI18n; import org.modeshape.graph.JaasSecurityContext; -import org.modeshape.graph.Location; -import org.modeshape.graph.Node; import org.modeshape.graph.SecurityContext; import org.modeshape.graph.Subgraph; import org.modeshape.graph.Workspace; @@ -87,19 +84,16 @@ import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.observe.Changes; import org.modeshape.graph.observe.Observable; import org.modeshape.graph.observe.Observer; -import org.modeshape.graph.property.DateTime; -import org.modeshape.graph.property.DateTimeFactory; import org.modeshape.graph.property.Name; import org.modeshape.graph.property.NamespaceRegistry; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.PathFactory; -import org.modeshape.graph.property.PathNotFoundException; import org.modeshape.graph.property.Property; import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.property.ValueFactories; -import org.modeshape.graph.property.ValueFactory; import org.modeshape.graph.property.basic.GraphNamespaceRegistry; import org.modeshape.graph.query.parse.QueryParsers; +import org.modeshape.graph.request.ChangeRequest; import org.modeshape.graph.request.InvalidWorkspaceException; import org.modeshape.jcr.RepositoryQueryManager.PushDown; import org.modeshape.jcr.api.Repository; @@ -108,6 +102,8 @@ import org.modeshape.jcr.query.JcrQomQueryParser; import org.modeshape.jcr.query.JcrSql2QueryParser; import org.modeshape.jcr.query.JcrSqlQueryParser; import org.modeshape.jcr.xpath.XPathQueryParser; +import com.google.common.collect.LinkedListMultimap; +import com.google.common.collect.Multimap; /** * Creates JCR {@link Session sessions} to an underlying repository (which may be a federated repository). @@ -410,9 +406,7 @@ public class JcrRepository implements Repository { private final ExecutionContext executionContext; private final RepositoryConnectionFactory connectionFactory; private final RepositoryNodeTypeManager repositoryTypeManager; - @GuardedBy( "lockManagersLock" ) - private final ConcurrentMap lockManagers; - private final Path locksPath; + private final RepositoryLockManager repositoryLockManager; private final Map options; private final String systemSourceName; private final String systemWorkspaceName; @@ -422,6 +416,11 @@ public class JcrRepository implements Repository { private final RepositoryObservationManager repositoryObservationManager; private final SecurityContext anonymousUserContext; private final QueryParsers queryParsers; + /** + * Immutable collection of objects observing changes to the system graph + */ + private final Collection jcrSystemObservers; + // Until the federated connector supports queries, we have to use a search engine ... private final RepositoryQueryManager queryManager; @@ -617,9 +616,6 @@ public class JcrRepository implements Repository { // Initialize required JCR descriptors. this.descriptors = initializeDescriptors(executionContext.getValueFactories(), descriptors); - this.lockManagers = new ConcurrentHashMap(); - this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); - // If the repository is to support searching ... if (Boolean.valueOf(this.options.get(Option.QUERY_EXECUTION_ENABLED)) && WORKSPACES_SHARE_SYSTEM_BRANCH) { // Determine whether the federated source and original source support queries and searches ... @@ -705,6 +701,15 @@ public class JcrRepository implements Repository { } this.anonymousUserContext = anonymousUserContext; + + repositoryLockManager = new RepositoryLockManager(this); + + this.jcrSystemObservers = Collections.unmodifiableCollection(Arrays.asList(new JcrSystemObserver[] { +repositoryLockManager, + })); + + // This observer picks up notification of changes to the system graph in a cluster. It's a NOP if there is no cluster. + this.repositoryObservationManager.register(new SystemChangeObserver()); } protected void addWorkspace( String workspaceName, @@ -873,6 +878,15 @@ public class JcrRepository implements Repository { } /** + * Returns the repository-level lock manager + * + * @return the repository-level lock manager + */ + RepositoryLockManager getRepositoryLockManager() { + return repositoryLockManager; + } + + /** * Get the options as configured for this repository. * * @return the unmodifiable options; never null @@ -1251,24 +1265,6 @@ public class JcrRepository implements Repository { } /** - * Returns the lock manager for the named workspace (if one already exists) or creates a new lock manager and returns it. This - * method is thread-safe. - * - * @param workspaceName the name of the workspace for which the lock manager should be returned - * @return the lock manager for the workspace; never null - */ - WorkspaceLockManager getLockManager( String workspaceName ) { - WorkspaceLockManager lockManager = lockManagers.get(workspaceName); - if (lockManager != null) return lockManager; - - lockManager = new WorkspaceLockManager(executionContext, this, workspaceName, locksPath); - WorkspaceLockManager newLockManager = lockManagers.putIfAbsent(workspaceName, lockManager); - - if (newLockManager != null) return newLockManager; - return lockManager; - } - - /** * Marks the given session as inactive (by removing it from the {@link #activeSessions active sessions map}. * * @param session the session to be marked as inactive @@ -1302,70 +1298,6 @@ public class JcrRepository implements Repository { return activeSessions; } - /** - * Iterates through the list of session-scoped locks in this repository, deleting any session-scoped locks that were created - * by a session that is no longer active. - */ - void cleanUpLocks() { - if (LOGGER.isTraceEnabled()) { - LOGGER.trace(JcrI18n.cleaningUpLocks.text()); - } - - Set activeSessions = activeSessions(); - Set activeSessionIds = new HashSet(activeSessions.size()); - - for (JcrSession activeSession : activeSessions) { - activeSessionIds.add(activeSession.sessionId()); - } - - Graph systemGraph = createSystemGraph(executionContext); - PathFactory pathFactory = executionContext.getValueFactories().getPathFactory(); - ValueFactory booleanFactory = executionContext.getValueFactories().getBooleanFactory(); - ValueFactory stringFactory = executionContext.getValueFactories().getStringFactory(); - - DateTimeFactory dateFactory = executionContext.getValueFactories().getDateFactory(); - DateTime now = dateFactory.create(); - DateTime newExpirationDate = now.plusMillis(JcrEngine.LOCK_EXTENSION_INTERVAL_IN_MILLIS); - - Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); - - Subgraph locksGraph = null; - try { - locksGraph = systemGraph.getSubgraphOfDepth(2).at(locksPath); - } catch (PathNotFoundException pnfe) { - // It's possible for this to run before the dna:locks child node gets added to the /jcr:system node. - return; - } - - for (Location lockLocation : locksGraph.getRoot().getChildren()) { - Node lockNode = locksGraph.getNode(lockLocation); - - Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED) - .getFirstValue()); - - if (!isSessionScoped) continue; - String lockingSession = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.LOCKING_SESSION).getFirstValue()); - - // Extend locks held by active sessions - if (activeSessionIds.contains(lockingSession)) { - systemGraph.set(ModeShapeLexicon.EXPIRATION_DATE).on(lockLocation).to(newExpirationDate); - } else { - DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE) - .getFirstValue()); - // Destroy expired locks (if it was still held by an active session, it would have been extended by now) - if (expirationDate.isBefore(now)) { - String workspaceName = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.WORKSPACE).getFirstValue()); - WorkspaceLockManager lockManager = lockManagers.get(workspaceName); - lockManager.unlock(executionContext, lockManager.createLock(lockNode)); - } - } - } - - if (LOGGER.isTraceEnabled()) { - LOGGER.trace(JcrI18n.cleanedUpLocks.text()); - } - } - protected static Properties getBundleProperties() { if (bundleProperties == null) { // This is idempotent, so we don't need to lock ... @@ -1712,7 +1644,7 @@ public class JcrRepository implements Repository { // These events were made locally and are being handled above, so we can ignore these ... return null; } - // Otherwise, the changes were recieved from another engine in the cluster and + // Otherwise, the changes were received from another engine in the cluster and // we do want to respond to these changes. However, the source name of the changes // needs to be altered to match the 'sourceName' ... return new Changes(changes.getProcessId(), changes.getContextId(), changes.getUserName(), sourceName, @@ -1752,4 +1684,61 @@ public class JcrRepository implements Repository { return this.observers.remove(observer); } } + + /** + * Observer that forwards changes to the system workspace in the system repository source to registered + * {@link JcrSystemObserver JcrSystemObservers}. This method is intended to support consistency of internal data structures in + * a cluster and only forwards changes from other processes. + */ + class SystemChangeObserver implements Observer { + + private final String processId; + private final String systemSourceName; + private final String systemWorkspaceName; + + SystemChangeObserver() { + processId = getExecutionContext().getProcessId(); + systemSourceName = getSystemSourceName(); + systemWorkspaceName = getSystemWorkspaceName(); + + assert processId != null; + assert systemSourceName != null; + assert systemWorkspaceName != null; + } + + @Override + public void notify( Changes changes ) { + + // Don't process changes from outside the system graph + if (!changes.getSourceName().equals(systemSourceName)) return; + + // Don't process changes from this repository + if (changes.getProcessId().equals(processId)) return; + + Multimap systemChanges = LinkedListMultimap.create(); + + for (ChangeRequest change : changes.getChangeRequests()) { + // Don't process changes from outside the system workspace + if (!systemWorkspaceName.equals(change.changedWorkspace())) continue; + + Path changedPath = change.changedLocation().getPath(); + if (changedPath == null) continue; + + for (JcrSystemObserver jcrSystemObserver : jcrSystemObservers) { + if (changedPath.isAtOrAbove(jcrSystemObserver.getObservedRootPath())) { + systemChanges.put(jcrSystemObserver, change); + } + } + } + + // Parcel out the new change objects + for (JcrSystemObserver jcrSystemObserver : systemChanges.keySet()) { + List changesForObserver = (List)systemChanges.get(jcrSystemObserver); + Changes filteredChanges = new Changes(changes.getProcessId(), changes.getContextId(), changes.getUserName(), + systemSourceName, changes.getTimestamp(), changesForObserver, + changes.getData()); + jcrSystemObserver.notify(filteredChanges); + } + } + } } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSystemObserver.java new file mode 100644 =================================================================== --- /dev/null (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSystemObserver.java (working copy) @@ -0,0 +1,10 @@ +package org.modeshape.jcr; + +import org.modeshape.graph.observe.Observer; +import org.modeshape.graph.property.Path; + +public interface JcrSystemObserver extends Observer { + + Path getObservedRootPath(); + +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (working copy) @@ -189,7 +189,7 @@ class JcrWorkspace implements Workspace { // // Set up and initialize the persistent JCR namespace registry ... this.workspaceRegistry = new JcrNamespaceRegistry(this.repository.getPersistentRegistry(), this.session); - this.lockManager = new JcrLockManager(session, repository.getLockManager(workspaceName)); + this.lockManager = new JcrLockManager(session, repository.getRepositoryLockManager().getLockManager(workspaceName)); } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java new file mode 100644 =================================================================== --- /dev/null (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (working copy) @@ -0,0 +1,239 @@ +package org.modeshape.jcr; + +import java.util.HashSet; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import net.jcip.annotations.ThreadSafe; +import org.modeshape.common.util.Logger; +import org.modeshape.graph.ExecutionContext; +import org.modeshape.graph.Graph; +import org.modeshape.graph.Location; +import org.modeshape.graph.Node; +import org.modeshape.graph.Subgraph; +import org.modeshape.graph.observe.Changes; +import org.modeshape.graph.property.DateTime; +import org.modeshape.graph.property.DateTimeFactory; +import org.modeshape.graph.property.Path; +import org.modeshape.graph.property.PathFactory; +import org.modeshape.graph.property.PathNotFoundException; +import org.modeshape.graph.property.Property; +import org.modeshape.graph.property.ValueFactory; +import org.modeshape.graph.property.Path.Segment; +import org.modeshape.graph.request.ChangeRequest; +import org.modeshape.graph.request.CreateNodeRequest; + +@ThreadSafe +class RepositoryLockManager implements JcrSystemObserver { + + private static final Logger LOGGER = Logger.getLogger(RepositoryLockManager.class); + + private final JcrRepository repository; + private final ConcurrentMap lockManagers; + private final Path locksPath; + + RepositoryLockManager( JcrRepository repository ) { + this.repository = repository; + ExecutionContext executionContext = repository.getExecutionContext(); + PathFactory pathFactory = executionContext.getValueFactories().getPathFactory(); + + this.lockManagers = new ConcurrentHashMap(); + this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); + + if (locksPath != null) { + + Property locksPrimaryType = executionContext.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, + ModeShapeLexicon.LOCKS); + + repository.createSystemGraph(executionContext).create(locksPath, locksPrimaryType).ifAbsent().and(); + } + + } + + /** + * Returns the lock manager for the named workspace (if one already exists) or creates a new lock manager and returns it. This + * method is thread-safe. + * + * @param workspaceName the name of the workspace for which the lock manager should be returned + * @return the lock manager for the workspace; never null + */ + WorkspaceLockManager getLockManager( String workspaceName ) { + WorkspaceLockManager lockManager = lockManagers.get(workspaceName); + if (lockManager != null) return lockManager; + + ExecutionContext executionContext = repository.getExecutionContext(); + lockManager = new WorkspaceLockManager(executionContext, this, workspaceName, locksPath); + WorkspaceLockManager newLockManager = lockManagers.putIfAbsent(workspaceName, lockManager); + + if (newLockManager != null) return newLockManager; + return lockManager; + } + + JcrGraph createSystemGraph( ExecutionContext context ) { + return repository.createSystemGraph(context); + } + + JcrGraph createWorkspaceGraph( String workspaceName, + ExecutionContext workspaceContext ) { + return repository.createWorkspaceGraph(workspaceName, workspaceContext); + } + + /** + * Iterates through the list of session-scoped locks in this repository, deleting any session-scoped locks that were created + * by a session that is no longer active. + */ + void cleanUpLocks() { + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(JcrI18n.cleaningUpLocks.text()); + } + + Set activeSessions = repository.activeSessions(); + Set activeSessionIds = new HashSet(activeSessions.size()); + + for (JcrSession activeSession : activeSessions) { + activeSessionIds.add(activeSession.sessionId()); + } + + ExecutionContext executionContext = repository.getExecutionContext(); + Graph systemGraph = createSystemGraph(executionContext); + PathFactory pathFactory = executionContext.getValueFactories().getPathFactory(); + ValueFactory booleanFactory = executionContext.getValueFactories().getBooleanFactory(); + ValueFactory stringFactory = executionContext.getValueFactories().getStringFactory(); + + DateTimeFactory dateFactory = executionContext.getValueFactories().getDateFactory(); + DateTime now = dateFactory.create(); + DateTime newExpirationDate = now.plusMillis(JcrEngine.LOCK_EXTENSION_INTERVAL_IN_MILLIS); + + Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); + + Subgraph locksGraph = null; + try { + locksGraph = systemGraph.getSubgraphOfDepth(2).at(locksPath); + } catch (PathNotFoundException pnfe) { + // It's possible for this to run before the dna:locks child node gets added to the /jcr:system node. + return; + } + + for (Location lockLocation : locksGraph.getRoot().getChildren()) { + Node lockNode = locksGraph.getNode(lockLocation); + + Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED).getFirstValue()); + + if (!isSessionScoped) continue; + String lockingSession = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.LOCKING_SESSION).getFirstValue()); + + // Extend locks held by active sessions + if (activeSessionIds.contains(lockingSession)) { + systemGraph.set(ModeShapeLexicon.EXPIRATION_DATE).on(lockLocation).to(newExpirationDate); + } else { + DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE).getFirstValue()); + // Destroy expired locks (if it was still held by an active session, it would have been extended by now) + if (expirationDate.isBefore(now)) { + String workspaceName = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.WORKSPACE).getFirstValue()); + WorkspaceLockManager lockManager = lockManagers.get(workspaceName); + lockManager.unlock(executionContext, lockManager.createLock(lockNode)); + } + } + } + + if (LOGGER.isTraceEnabled()) { + LOGGER.trace(JcrI18n.cleanedUpLocks.text()); + } + } + + @Override + public Path getObservedRootPath() { + return locksPath; + } + + @Override + public void notify( Changes changes ) { + for (ChangeRequest change : changes.getChangeRequests()) { + assert change.changedLocation().hasPath(); + + Path changedPath = change.changedLocation().getPath(); + assert locksPath.isAncestorOf(changedPath); + + Segment rawUuid = changedPath.getLastSegment(); + UUID lockedNodeUuid = UUID.fromString(string(rawUuid)); + assert lockedNodeUuid != null; + + String workspaceName = change.changedWorkspace(); + WorkspaceLockManager workspaceManager = getLockManager(workspaceName); + assert workspaceManager != null; + + switch (change.getType()) { + case CREATE_NODE: + CreateNodeRequest create = (CreateNodeRequest) change; + + Property lockOwnerProp= null; + Property lockUuidProp = null; + Property isDeepProp = null; + Property isSessionScopedProp = null; + + for (Property prop : create.properties()) { + if (JcrLexicon.LOCK_OWNER.equals(prop.getName())) { + lockOwnerProp = prop; + } + else if (JcrLexicon.LOCK_IS_DEEP.equals(prop.getName())) { + isDeepProp = prop; + } + else if (ModeShapeLexicon.IS_HELD_BY_SESSION.equals(prop.getName())) { + isSessionScopedProp = prop; + } + else if (JcrLexicon.UUID.equals(prop.getName())) { + isSessionScopedProp = prop; + } + } + + String lockOwner = firstString(lockOwnerProp); + UUID lockUuid = firstUuid(lockUuidProp); + boolean isDeep = firstBoolean(isDeepProp); + boolean isSessionScoped = firstBoolean(isSessionScopedProp); + + workspaceManager.lockNodeInternally(lockOwner, lockUuid, lockedNodeUuid, isDeep, isSessionScoped); + + break; + case DELETE_BRANCH: + boolean success = workspaceManager.unlockNodeInternally(lockedNodeUuid); + + assert success : "No internal lock existed for node " + lockedNodeUuid.toString(); + + break; + default: + assert false :"Unexpected change request: " + change; + } + + } + } + + private final String string(Segment rawString) { + ExecutionContext context = repository.getExecutionContext(); + return context.getValueFactories().getStringFactory().create(rawString); + } + + private final String firstString(Property property) { + if (property == null) return null; + Object firstValue = property.getFirstValue(); + + ExecutionContext context = repository.getExecutionContext(); + return context.getValueFactories().getStringFactory().create(firstValue); + } + + private final UUID firstUuid(Property property) { + if (property == null) return null; + Object firstValue = property.getFirstValue(); + + ExecutionContext context = repository.getExecutionContext(); + return context.getValueFactories().getUuidFactory().create(firstValue); + } + + private final boolean firstBoolean(Property property) { + if (property == null) return false; + Object firstValue = property.getFirstValue(); + + ExecutionContext context = repository.getExecutionContext(); + return context.getValueFactories().getBooleanFactory().create(firstValue); + } +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/WorkspaceLockManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/WorkspaceLockManager.java (revision 2009) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/WorkspaceLockManager.java (working copy) @@ -57,26 +57,20 @@ class WorkspaceLockManager { private final ExecutionContext context; private final Path locksPath; - private final JcrRepository repository; + private final RepositoryLockManager repositoryLockManager; private final String workspaceName; private final ConcurrentMap workspaceLocksByNodeUuid; WorkspaceLockManager( ExecutionContext context, - JcrRepository repository, + RepositoryLockManager repositoryLockManager, String workspaceName, Path locksPath ) { this.context = context; - this.repository = repository; + this.repositoryLockManager = repositoryLockManager; this.workspaceName = workspaceName; this.locksPath = locksPath; this.workspaceLocksByNodeUuid = new ConcurrentHashMap(); - - Property locksPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCKS); - - if (locksPath != null) { - repository.createSystemGraph(context).create(locksPath, locksPrimaryType).ifAbsent().and(); - } } /** @@ -109,35 +103,44 @@ class WorkspaceLockManager { ExecutionContext sessionContext = session.getExecutionContext(); String lockOwner = session.getUserID(); - ModeShapeLock lock = createLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped); - - Graph.Batch batch = repository.createSystemGraph(sessionContext).batch(); - - PropertyFactory propFactory = sessionContext.getPropertyFactory(); - PathFactory pathFactory = sessionContext.getValueFactories().getPathFactory(); - Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner); - Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep); DateTimeFactory dateFactory = sessionContext.getValueFactories().getDateFactory(); DateTime expirationDate = dateFactory.create(); expirationDate = expirationDate.plusMillis(JcrEngine.LOCK_EXTENSION_INTERVAL_IN_MILLIS); - batch.create(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString())), - propFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCK), - propFactory.create(ModeShapeLexicon.WORKSPACE, workspaceName), - propFactory.create(ModeShapeLexicon.LOCKED_UUID, nodeUuid.toString()), - propFactory.create(ModeShapeLexicon.IS_SESSION_SCOPED, isSessionScoped), - propFactory.create(ModeShapeLexicon.LOCKING_SESSION, session.sessionId()), - propFactory.create(ModeShapeLexicon.EXPIRATION_DATE, expirationDate), - // This gets set after the lock succeeds and the lock token gets added to the session - propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, false), - lockOwnerProp, - lockIsDeepProp).ifAbsent().and(); - batch.execute(); + lockNodeInSystemGraph(sessionContext, + nodeUuid, + lockUuid, + workspaceName, + isSessionScoped, + isDeep, + session.sessionId(), + session.getUserID(), + expirationDate); + + try { + lockNodeInRepository(session, nodeUuid, session.getUserID(), isDeep); + } catch (LockFailedException lfe) { + // Attempt to lock node at the repo level failed - cancel lock + unlockNodeInSystemGraph(session.getExecutionContext(), nodeUuid); + throw new RepositoryException(lfe); + } + + ModeShapeLock lock = lockNodeInternally(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped); - lockNodeInRepository(session, nodeUuid, lockOwnerProp, lockIsDeepProp, lock, isDeep); session.cache().refreshProperties(Location.create(nodeUuid)); - workspaceLocksByNodeUuid.put(nodeUuid, lock); + + return lock; + } + + ModeShapeLock lockNodeInternally( String lockOwner, + UUID lockUuid, + UUID lockedNodeUuid, + boolean isDeep, + boolean isSessionScoped ) { + ModeShapeLock lock = createLock(lockOwner, lockUuid, lockedNodeUuid, isDeep, isSessionScoped); + + workspaceLocksByNodeUuid.put(lockedNodeUuid, lock); return lock; } @@ -171,39 +174,58 @@ class WorkspaceLockManager { * * @param session the session in which the node is being locked and that loaded the node * @param nodeUuid the UUID of the node to lock - * @param lockOwnerProp an existing property with name {@link JcrLexicon#LOCK_OWNER} and the value being the name of the lock - * owner - * @param lockIsDeepProp an existing property with name {@link JcrLexicon#LOCK_IS_DEEP} and the value being {@code true} if - * the lock should include all descendants of the locked node or {@code false} if the lock should only include the - * specified node and not its descendants - * @param lock the internal lock representation + * @param lockOwner a string containing the lock owner's name * @param isDeep {@code true} if the lock should include all descendants of the locked node or {@code false} if the lock * should only include the specified node and not its descendants. This value is redundant with the lockIsDeep * parameter, but is included separately as a minor performance optimization + * @throws LockFailedException if the repository supports locking nodes, but could not lock this particular node * @throws RepositoryException if the repository in which the node represented by {@code nodeUuid} supports locking but * signals that the lock for the node cannot be acquired */ void lockNodeInRepository( JcrSession session, UUID nodeUuid, - Property lockOwnerProp, - Property lockIsDeepProp, - ModeShapeLock lock, - boolean isDeep ) throws RepositoryException { + String lockOwner, + boolean isDeep ) throws LockFailedException, RepositoryException { + PropertyFactory propFactory = session.getExecutionContext().getPropertyFactory(); + Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner); + Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep); + // Write them directly to the underlying graph - Graph.Batch workspaceBatch = repository.createWorkspaceGraph(workspaceName, session.getExecutionContext()).batch(); + Graph.Batch workspaceBatch = repositoryLockManager.createWorkspaceGraph(workspaceName, session.getExecutionContext()).batch(); workspaceBatch.set(lockOwnerProp, lockIsDeepProp).on(nodeUuid); if (isDeep) { workspaceBatch.lock(nodeUuid).andItsDescendants().withDefaultTimeout(); } else { workspaceBatch.lock(nodeUuid).only().withDefaultTimeout(); } - try { workspaceBatch.execute(); - } catch (LockFailedException lfe) { - // Attempt to lock node at the repo level failed - cancel lock - unlock(session.getExecutionContext(), lock); - throw new RepositoryException(lfe); - } + + } + + private final void lockNodeInSystemGraph( ExecutionContext sessionContext, + UUID nodeUuid, + UUID lockUuid, + String workspaceName, + boolean isSessionScoped, + boolean lockIsDeep, + String sessionId, + String lockOwner, + DateTime expirationDate ) { + PathFactory pathFactory = sessionContext.getValueFactories().getPathFactory(); + PropertyFactory propFactory = sessionContext.getPropertyFactory(); + + Graph graph = repositoryLockManager.createSystemGraph(sessionContext); + graph.create(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString())), + propFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.LOCK), + propFactory.create(ModeShapeLexicon.WORKSPACE, workspaceName), + propFactory.create(ModeShapeLexicon.LOCKED_UUID, nodeUuid.toString()), + propFactory.create(ModeShapeLexicon.IS_SESSION_SCOPED, isSessionScoped), + propFactory.create(ModeShapeLexicon.LOCKING_SESSION, sessionId), + propFactory.create(ModeShapeLexicon.EXPIRATION_DATE, expirationDate), + // This gets set after the lock succeeds and the lock token gets added to the session + propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, false), + propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner), + propFactory.create(JcrLexicon.LOCK_IS_DEEP, lockIsDeep)).ifAbsent().and(); } @@ -216,17 +238,13 @@ class WorkspaceLockManager { void unlock( ExecutionContext sessionExecutionContext, ModeShapeLock lock ) { try { - PathFactory pathFactory = sessionExecutionContext.getValueFactories().getPathFactory(); - // Remove the lock node under the /jcr:system branch ... - Graph.Batch batch = repository.createSystemGraph(sessionExecutionContext).batch(); - batch.delete(pathFactory.create(locksPath, pathFactory.createSegment(lock.getUuid().toString()))); - batch.execute(); + unlockNodeInSystemGraph(sessionExecutionContext, lock.getUuid()); // Unlock the node in the repository graph ... unlockNodeInRepository(sessionExecutionContext, lock); - workspaceLocksByNodeUuid.remove(lock.nodeUuid); + unlockNodeInternally(lock.nodeUuid); } catch (PathNotFoundException pnfe) { /* * This can legitimately happen if there is a session-scoped lock on a node which is then deleted by the lock owner and the lock @@ -254,7 +272,7 @@ class WorkspaceLockManager { */ void unlockNodeInRepository( ExecutionContext sessionExecutionContext, ModeShapeLock lock ) { - Graph.Batch workspaceBatch = repository.createWorkspaceGraph(this.workspaceName, sessionExecutionContext).batch(); + Graph.Batch workspaceBatch = repositoryLockManager.createWorkspaceGraph(this.workspaceName, sessionExecutionContext).batch(); workspaceBatch.remove(JcrLexicon.LOCK_OWNER, JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid); workspaceBatch.unlock(lock.nodeUuid); @@ -262,6 +280,18 @@ class WorkspaceLockManager { workspaceBatch.execute(); } + void unlockNodeInSystemGraph( ExecutionContext sessionExecutionContext, + UUID lockUuid ) { + PathFactory pathFactory = sessionExecutionContext.getValueFactories().getPathFactory(); + Graph systemGraph = repositoryLockManager.createSystemGraph(sessionExecutionContext); + systemGraph.delete(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString()))); + + } + + boolean unlockNodeInternally( UUID lockedNodeUuid ) { + return workspaceLocksByNodeUuid.remove(lockedNodeUuid) != null; + } + /** * Checks whether the given lock token is currently held by any session by querying the lock record in the underlying * repository. @@ -280,9 +310,8 @@ class WorkspaceLockManager { PathFactory pathFactory = context.getValueFactories().getPathFactory(); try { - org.modeshape.graph.Node lockNode = repository.createSystemGraph(context) - .getNodeAt(pathFactory.create(locksPath, - pathFactory.createSegment(lockToken))); + org.modeshape.graph.Node lockNode = repositoryLockManager.createSystemGraph(context).getNodeAt(pathFactory.create(locksPath, + pathFactory.createSegment(lockToken))); return booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_HELD_BY_SESSION).getFirstValue()); } catch (PathNotFoundException pnfe) { @@ -310,9 +339,8 @@ class WorkspaceLockManager { PropertyFactory propFactory = context.getPropertyFactory(); PathFactory pathFactory = context.getValueFactories().getPathFactory(); - repository.createSystemGraph(context) - .set(propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, value)) - .on(pathFactory.create(locksPath, pathFactory.createSegment(lockToken))); + repositoryLockManager.createSystemGraph(context).set(propFactory.create(ModeShapeLexicon.IS_HELD_BY_SESSION, value)).on(pathFactory.create(locksPath, + pathFactory.createSegment(lockToken))); } /** Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrNodeTest.java (working copy) @@ -668,11 +668,12 @@ public class AbstractJcrNodeTest extends AbstractJcrTest { Workspace workspace2 = mock(Workspace.class); JcrRepository repository2 = mock(JcrRepository.class); + RepositoryLockManager repoLockManager2 = mock(RepositoryLockManager.class); when(jcrSession2.getWorkspace()).thenReturn(workspace2); when(jcrSession2.getRepository()).thenReturn(repository2); when(workspace2.getName()).thenReturn("workspace2"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository2, "workspace2", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repoLockManager2, "workspace2", null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); @@ -701,11 +702,12 @@ public class AbstractJcrNodeTest extends AbstractJcrTest { Workspace workspace2 = mock(Workspace.class); JcrRepository repository2 = mock(JcrRepository.class); + RepositoryLockManager repoLockManager2 = mock(RepositoryLockManager.class); when(jcrSession2.getWorkspace()).thenReturn(workspace2); when(jcrSession2.getRepository()).thenReturn(repository2); when(workspace2.getName()).thenReturn("workspace1"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository2, "workspace2", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repoLockManager2, "workspace2", null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); @@ -737,7 +739,8 @@ public class AbstractJcrNodeTest extends AbstractJcrTest { when(jcrSession2.getRepository()).thenReturn(repository); when(workspace2.getName()).thenReturn("workspace1"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository, "workspace1", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository.getRepositoryLockManager(), "workspace1", + null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrPropertyTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrPropertyTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrPropertyTest.java (working copy) @@ -144,7 +144,8 @@ public class AbstractJcrPropertyTest extends AbstractJcrTest { SessionCache cache2 = new SessionCache(jcrSession2, store2.getCurrentWorkspaceName(), context, nodeTypes, store2); Workspace workspace2 = mock(Workspace.class); - Repository repository2 = mock(Repository.class); + Repository repository2 = mock(JcrRepository.class); + when(jcrSession2.getWorkspace()).thenReturn(workspace2); when(jcrSession2.getRepository()).thenReturn(repository2); when(workspace2.getName()).thenReturn("workspace1"); @@ -173,11 +174,12 @@ public class AbstractJcrPropertyTest extends AbstractJcrTest { Workspace workspace2 = mock(Workspace.class); JcrRepository repository2 = mock(JcrRepository.class); + RepositoryLockManager repoLockManager2 = mock(RepositoryLockManager.class); when(jcrSession2.getWorkspace()).thenReturn(workspace2); when(jcrSession2.getRepository()).thenReturn(repository2); when(workspace2.getName()).thenReturn("workspace2"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository2, "workspace2", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repoLockManager2, "workspace2", null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); @@ -211,11 +213,12 @@ public class AbstractJcrPropertyTest extends AbstractJcrTest { Workspace workspace2 = mock(Workspace.class); JcrRepository repository2 = mock(JcrRepository.class); + RepositoryLockManager repoLockManager2 = mock(RepositoryLockManager.class); when(jcrSession2.getWorkspace()).thenReturn(workspace2); when(jcrSession2.getRepository()).thenReturn(repository2); when(workspace2.getName()).thenReturn("workspace1"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository2, "workspace2", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repoLockManager2, "workspace2", null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); @@ -252,7 +255,7 @@ public class AbstractJcrPropertyTest extends AbstractJcrTest { when(jcrSession2.getRepository()).thenReturn(repository); when(workspace2.getName()).thenReturn("workspace1"); - WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repository, "workspace2", null); + WorkspaceLockManager lockManager = new WorkspaceLockManager(context, repoLockManager, "workspace2", null); JcrLockManager jcrLockManager = new JcrLockManager(jcrSession2, lockManager); when(jcrSession2.lockManager()).thenReturn(jcrLockManager); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractJcrTest.java (working copy) @@ -54,6 +54,7 @@ public abstract class AbstractJcrTest { protected static ExecutionContext context; protected static JcrRepository repository; + protected static RepositoryLockManager repoLockManager; protected static RepositoryNodeTypeManager rntm; protected InMemoryRepositorySource source; protected Graph store; @@ -79,6 +80,9 @@ public abstract class AbstractJcrTest { repository = mock(JcrRepository.class); when(repository.getExecutionContext()).thenReturn(context); + repoLockManager = mock(RepositoryLockManager.class); + when(repository.getRepositoryLockManager()).thenReturn(repoLockManager); + rntm = new RepositoryNodeTypeManager(repository, true); try { rntm.registerNodeTypes(new CndNodeTypeSource(new String[] {"/org/modeshape/jcr/jsr_283_builtins.cnd", @@ -132,7 +136,7 @@ public abstract class AbstractJcrTest { when(jcrSession.isLive()).thenReturn(true); when(jcrSession.getUserID()).thenReturn("username"); - lockManager = new WorkspaceLockManager(context, repository, workspaceName, null); + lockManager = new WorkspaceLockManager(context, repoLockManager, workspaceName, null); jcrLockManager = new JcrLockManager(jcrSession, lockManager); when(jcrSession.lockManager()).thenReturn(jcrLockManager); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/AbstractSessionTest.java (working copy) @@ -79,6 +79,8 @@ public abstract class AbstractSessionTest { protected QueryParsers parsers; @Mock protected JcrRepository repository; + @Mock + protected RepositoryLockManager repoLockManager; protected void beforeEach() throws Exception { MockitoAnnotations.initMocks(this); @@ -140,6 +142,7 @@ public abstract class AbstractSessionTest { } }); when(this.repository.getRepositoryObservable()).thenReturn(new MockObservable()); + when(repository.getRepositoryLockManager()).thenReturn(repoLockManager); // Stub out the repository, since we only need a few methods ... repoTypeManager = new RepositoryNodeTypeManager(repository, true); @@ -160,9 +163,9 @@ public abstract class AbstractSessionTest { this.repoTypeManager.projectOnto(graph, pathFactory.create("/jcr:system/jcr:nodeTypes")); Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); - workspaceLockManager = new WorkspaceLockManager(context, repository, workspaceName, locksPath); + workspaceLockManager = new WorkspaceLockManager(context, repoLockManager, workspaceName, locksPath); - when(repository.getLockManager(anyString())).thenAnswer(new Answer() { + when(repoLockManager.getLockManager(anyString())).thenAnswer(new Answer() { public WorkspaceLockManager answer( InvocationOnMock invocation ) throws Throwable { return workspaceLockManager; } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrRepositoryTest.java (working copy) @@ -563,7 +563,7 @@ public class JcrRepositoryTest { assertThat(readerNode.isLocked(), is(true)); // No locks should have changed yet. - repository.cleanUpLocks(); + repository.getRepositoryLockManager().cleanUpLocks(); assertThat(lockedNode.isLocked(), is(true)); assertThat(readerNode.isLocked(), is(true)); @@ -578,7 +578,7 @@ public class JcrRepositoryTest { Thread.sleep(JcrEngine.LOCK_EXTENSION_INTERVAL_IN_MILLIS + 100); // The locker thread should be inactive and the lock cleaned up - repository.cleanUpLocks(); + repository.getRepositoryLockManager().cleanUpLocks(); assertThat(readerNode.isLocked(), is(false)); } Index: modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java (revision 2009) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/WorkspaceLockManagerTest.java (working copy) @@ -25,7 +25,6 @@ import org.modeshape.graph.property.Name; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.PathFactory; import org.modeshape.graph.property.Property; -import org.modeshape.graph.property.PropertyFactory; import org.modeshape.graph.request.LockBranchRequest; import org.modeshape.graph.request.Request; import org.modeshape.graph.request.UnlockBranchRequest; @@ -50,6 +49,8 @@ public class WorkspaceLockManagerTest { private RepositoryConnectionFactory connectionFactory; @Mock protected JcrRepository repository; + @Mock + protected RepositoryLockManager repoLockManager; @Before public void beforeEach() { @@ -81,13 +82,23 @@ public class WorkspaceLockManagerTest { }); Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, ModeShapeLexicon.LOCKS); - workspaceLockManager = new WorkspaceLockManager(context, repository, workspaceName, locksPath); + workspaceLockManager = new WorkspaceLockManager(context, repoLockManager, workspaceName, locksPath); - when(repository.getLockManager(anyString())).thenAnswer(new Answer() { + when(repoLockManager.getLockManager(anyString())).thenAnswer(new Answer() { public WorkspaceLockManager answer( InvocationOnMock invocation ) throws Throwable { return workspaceLockManager; } }); + when(repoLockManager.createWorkspaceGraph(anyString(), (ExecutionContext)anyObject())).thenAnswer(new Answer() { + public Graph answer( InvocationOnMock invocation ) throws Throwable { + return graph; + } + }); + when(repoLockManager.createSystemGraph(context)).thenAnswer(new Answer() { + public Graph answer( InvocationOnMock invocation ) throws Throwable { + return graph; + } + }); // Stub out the repository, since we only need a few methods ... repoTypeManager = new RepositoryNodeTypeManager(repository, true); @@ -135,18 +146,15 @@ public class WorkspaceLockManagerTest { @Test public void shouldCreateLockRequestWhenLockingNode() throws RepositoryException { - ModeShapeLock lock = workspaceLockManager.createLock("testOwner", UUID.randomUUID(), validUuid, false, false); - PropertyFactory propFactory = context.getPropertyFactory(); String lockOwner = "testOwner"; boolean isDeep = false; - Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner); - Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep); - JcrSession session = mock(JcrSession.class); when(session.getExecutionContext()).thenReturn(context); - workspaceLockManager.lockNodeInRepository(session, validUuid, lockOwnerProp, lockIsDeepProp, lock, isDeep); + workspaceLockManager.lockNodeInRepository(session, validUuid, lockOwner, isDeep); + // At the moment, a VerifyWorkspaceRequest is being executed when the graph is created. + executedRequests.poll(); assertNextRequestIsLock(validLocation, LockScope.SELF_ONLY, 0); } @@ -155,6 +163,8 @@ public class WorkspaceLockManagerTest { ModeShapeLock lock = workspaceLockManager.createLock("testOwner", UUID.randomUUID(), validUuid, false, false); workspaceLockManager.unlockNodeInRepository(context, lock); + // At the moment, a VerifyWorkspaceRequest is being executed when the graph is created. + executedRequests.poll(); assertNextRequestIsUnlock(validLocation); }