Index: deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedLock.java =================================================================== --- deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedLock.java (revision 2107) +++ deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedLock.java (working copy) @@ -33,6 +33,7 @@ import org.jboss.managed.api.annotation.ManagementProperty; import org.jboss.managed.api.annotation.ViewUse; import org.joda.time.DateTime; import org.modeshape.common.util.CheckArg; +import org.modeshape.jcr.JcrLockSnapshot; /** * The ManagedSession class is a JBoss managed object of a ModeShape lock. @@ -107,7 +108,7 @@ public final class ManagedLock implements ModeShapeManagedObject { String id, String owner, boolean deep ) { - CheckArg.isNotEmpty(workspaceName, "workspaceName"); + CheckArg.isNotNull(workspaceName, "workspaceName"); CheckArg.isNotEmpty(sessionId, "sessionId"); CheckArg.isNotNull(expiration, "expiration"); CheckArg.isNotEmpty(id, "id"); @@ -122,6 +123,12 @@ public final class ManagedLock implements ModeShapeManagedObject { this.workspaceName = workspaceName; } + public ManagedLock(JcrLockSnapshot snapshot) { + this(snapshot.getWorkspaceName(), snapshot.isSessionBased(), snapshot.getSessionId(), + new DateTime(snapshot.getExpiration().getMillisecondsInUtc()), + snapshot.getLockId(), snapshot.getOwner(), snapshot.isDeep()); + } + /** * Obtains the lock's expiration time. This is a JBoss managed readonly property. * Index: deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedRepository.java =================================================================== --- deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedRepository.java (revision 2107) +++ deploy/jbossas/modeshape-jbossas-service/src/main/java/org/modeshape/jboss/managed/ManagedRepository.java (working copy) @@ -30,9 +30,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; - import javax.jcr.Repository; - import org.jboss.managed.api.ManagedOperation.Impact; import org.jboss.managed.api.annotation.ManagementOperation; import org.jboss.managed.api.annotation.ManagementParameter; @@ -41,6 +39,7 @@ import org.jboss.managed.api.annotation.ViewUse; import org.jboss.metatype.api.annotations.MetaMapping; import org.joda.time.DateTime; import org.modeshape.common.util.CheckArg; +import org.modeshape.jcr.JcrLockSnapshot; import org.modeshape.jcr.JcrRepository; import org.modeshape.jcr.JcrRepository.Option; @@ -194,12 +193,11 @@ public class ManagedRepository implements ModeShapeManagedObject { public List listLocks( Comparator lockSorter ) { CheckArg.isNotNull(lockSorter, "lockSorter"); - // TODO implement listLocks(Comparator) - List locks = new ArrayList(); + List snapshots = repository.getAllLocks(); + List locks = new ArrayList(snapshots.size()); - // create temporary date - for (int i = 0; i < 5; ++i) { - locks.add(new ManagedLock("workspace-" + i, true, "sessionId-1", new DateTime(), "id-" + i, "owner-" + i, true)); + for (JcrLockSnapshot snapshot : snapshots) { + locks.add(new ManagedLock(snapshot)); } // sort @@ -249,8 +247,7 @@ public class ManagedRepository implements ModeShapeManagedObject { */ @ManagementOperation( description = "Removes the lock with the specified ID", impact = Impact.WriteOnly, params = {@ManagementParameter( name = "lockId", description = "The lock identifier" )} ) public boolean removeLock( String lockId ) { - // TODO implement removeLockWithLockToken() - return false; + return repository.removeLock(lockId); } /** Index: deploy/jbossas/modeshape-jbossas-service/src/test/java/org/modeshape/jboss/managed/ManagedEngineTest.java =================================================================== --- deploy/jbossas/modeshape-jbossas-service/src/test/java/org/modeshape/jboss/managed/ManagedEngineTest.java (revision 2107) +++ deploy/jbossas/modeshape-jbossas-service/src/test/java/org/modeshape/jboss/managed/ManagedEngineTest.java (working copy) @@ -29,6 +29,10 @@ import static org.junit.Assert.assertThat; import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; +import javax.jcr.Node; +import javax.jcr.Session; +import javax.jcr.lock.Lock; +import javax.jcr.lock.LockManager; import org.junit.After; import org.junit.Before; import org.junit.BeforeClass; @@ -36,6 +40,7 @@ import org.junit.Test; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.jcr.JcrConfiguration; import org.modeshape.jcr.JcrEngine; +import org.modeshape.jcr.JcrRepository; import org.modeshape.jcr.JcrRepository.Option; /** @@ -44,6 +49,7 @@ import org.modeshape.jcr.JcrRepository.Option; public class ManagedEngineTest { private static Set REPO_NAMES = null; + private static String REPO_NAME = "JCR Repository"; private ManagedEngine me; @@ -53,7 +59,7 @@ public class ManagedEngineTest { @BeforeClass public static void beforeAll() throws Exception { REPO_NAMES = new HashSet(); - REPO_NAMES.add("JCR Repository"); + REPO_NAMES.add(REPO_NAME); } @@ -66,7 +72,7 @@ public class ManagedEngineTest { .loadedFromClasspath() .setDescription("description") .and() - .repository("JCR Repository") +.repository(REPO_NAME) .setSource("Source2") .setOption(Option.JAAS_LOGIN_CONFIG_NAME, "test") .and() @@ -111,4 +117,78 @@ public class ManagedEngineTest { assertThat(me.getRepositories().size(), is(REPO_NAMES.size())); } + + @Test + public void shouldGetLocks() throws Exception { + JcrRepository repo = engine.getRepository(REPO_NAME); + ManagedRepository managedRepo = me.getRepository(REPO_NAME); + + assertThat(managedRepo, is(notNullValue())); + assertThat(repo, is(notNullValue())); + + assertThat(managedRepo.listLocks().size(), is(0)); + + Session session = repo.login(); + Node rootNode = session.getRootNode(); + + final String NODE_NAME = "lockableNode"; + Node lockableNode = rootNode.addNode(NODE_NAME); + lockableNode.addMixin("mix:lockable"); + session.save(); + + LockManager lockManager = session.getWorkspace().getLockManager(); + lockManager.lock(lockableNode.getPath(), true, false, -1, null); + + assertThat(managedRepo.listLocks().size(), is(1)); + ManagedLock managedLock = managedRepo.listLocks().get(0); + Lock lock = lockManager.getLock(lockableNode.getPath()); + + assertThat(session.getWorkspace().getName(), is(managedLock.getWorkspaceName())); + assertThat(lock.getLockOwner(), is(managedLock.getOwner())); + assertThat(lock.isDeep(), is(managedLock.isDeep())); + assertThat(lock.isSessionScoped(), is(managedLock.isSessionBased())); + assertThat(session.getUserID(), is(managedLock.getOwner())); + + lockManager.unlock(lockableNode.getPath()); + assertThat(managedRepo.listLocks().size(), is(0)); + } + + @Test + public void shouldRemoveValidLock() throws Exception { + JcrRepository repo = engine.getRepository(REPO_NAME); + ManagedRepository managedRepo = me.getRepository(REPO_NAME); + + assertThat(managedRepo, is(notNullValue())); + assertThat(repo, is(notNullValue())); + + assertThat(managedRepo.listLocks().size(), is(0)); + + Session session = repo.login(); + Node rootNode = session.getRootNode(); + + final String NODE_NAME = "lockableNode"; + Node lockableNode = rootNode.addNode(NODE_NAME); + lockableNode.addMixin("mix:lockable"); + session.save(); + + LockManager lockManager = session.getWorkspace().getLockManager(); + lockManager.lock(lockableNode.getPath(), true, false, -1, null); + + assertThat(managedRepo.listLocks().size(), is(1)); + ManagedLock managedLock = managedRepo.listLocks().get(0); + + boolean result = managedRepo.removeLock(managedLock.getId()); + assertThat(result, is(true)); + + assertThat(managedRepo.listLocks().size(), is(0)); + assertThat(lockManager.isLocked(lockableNode.getPath()), is(false)); + + } + + @Test + public void shouldNotRemoveInvalidLock() throws Exception { + ManagedRepository managedRepo = me.getRepository(REPO_NAME); + + assertThat(managedRepo.removeLock("notAValidLockId"), is(false)); + } } Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLockSnapshot.java new file mode 100644 =================================================================== --- /dev/null (revision 2107) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrLockSnapshot.java (working copy) @@ -0,0 +1,91 @@ +package org.modeshape.jcr; + +import net.jcip.annotations.Immutable; +import org.modeshape.graph.property.DateTime; + +@Immutable +public class JcrLockSnapshot { + + /** + * The JBoss managed readonly property indicating if this lock is a deep lock. + */ + private final boolean deep; + + /** + * The JBoss managed readonly property for the lock's expiration time. The expiration time is never null. + */ + private final DateTime expiration; + + /** + * The JBoss managed readonly property for the lock's identifier. The ID is never null and never empty. + */ + private final String lockId; + + /** + * The JBoss managed readonly property for the lock's session identifier. The session ID is never null and never + * empty. + */ + private final String sessionId; + + /** + * The JBoss managed readonly property for the lock's owner. The owner is never null and never empty. + */ + private final String owner; + + /** + * The JBoss managed readonly property indicating if this lock is a session-based lock. + */ + private final boolean sessionBased; + + /** + * The JBoss managed readonly property for the lock's workspace name. The workspace name is never null and never + * empty. + */ + private final String workspaceName; + + public JcrLockSnapshot( boolean deep, + DateTime expiration, + String id, + String sessionId, + String owner, + boolean sessionBased, + String workspaceName ) { + super(); + this.deep = deep; + this.expiration = expiration; + this.lockId = id; + this.sessionId = sessionId; + this.owner = owner; + this.sessionBased = sessionBased; + this.workspaceName = workspaceName; + } + + public boolean isDeep() { + return deep; + } + + public DateTime getExpiration() { + return expiration; + } + + public String getLockId() { + return lockId; + } + + public String getSessionId() { + return sessionId; + } + + public String getOwner() { + return owner; + } + + public boolean isSessionBased() { + return sessionBased; + } + + public String getWorkspaceName() { + return workspaceName; + } + +} Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 2107) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -907,6 +907,17 @@ public class JcrRepository implements Repository { } /** + * @return snapshots of each of the currently active locks + */ + public List getAllLocks() { + return repositoryLockManager.allLocks(); + } + + public boolean removeLock( String lockId ) { + return repositoryLockManager.removeLock(lockId); + } + + /** * Get the options as configured for this repository. * * @return the unmodifiable options; never null Index: modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (revision 2107) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (working copy) @@ -1,6 +1,8 @@ package org.modeshape.jcr; +import java.util.ArrayList; import java.util.HashSet; +import java.util.List; import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; @@ -15,6 +17,7 @@ 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.NameFactory; import org.modeshape.graph.property.Path; import org.modeshape.graph.property.PathFactory; import org.modeshape.graph.property.PathNotFoundException; @@ -23,6 +26,7 @@ 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; +import org.modeshape.jcr.WorkspaceLockManager.ModeShapeLock; @ThreadSafe class RepositoryLockManager implements JcrSystemObserver { @@ -142,6 +146,72 @@ class RepositoryLockManager implements JcrSystemObserver { } } + List allLocks() { + Graph systemGraph = repository.createSystemGraph(repository.getExecutionContext()); + Subgraph locksGraph = systemGraph.getSubgraphOfDepth(2).at(locksPath); + + Node parentNode = locksGraph.getRoot(); + List snapshots = new ArrayList(parentNode.getChildrenSegments().size()); + + for (Location lockLocation : parentNode.getChildren()) { + Node lockNode = locksGraph.getNode(lockLocation); + + boolean deep = firstBoolean(lockNode.getProperty(JcrLexicon.LOCK_IS_DEEP)); + boolean sessionScoped = firstBoolean(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED)); + String owner = firstString(lockNode.getProperty(JcrLexicon.LOCK_OWNER)); + String workspaceName = firstString(lockNode.getProperty(ModeShapeLexicon.WORKSPACE)); + String lockId = lockLocation.getPath().getLastSegment().getName().getLocalName(); + String sessionId = firstString(lockNode.getProperty(ModeShapeLexicon.LOCKING_SESSION)); + DateTime expiration = firstDate(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE)); + + JcrLockSnapshot snapshot = new JcrLockSnapshot(deep, expiration, lockId, sessionId, owner, sessionScoped, + workspaceName); + snapshots.add(snapshot); + } + + return snapshots; + } + + boolean removeLock( String lockId ) { + UUID lockUuid = null; + try { + lockUuid = UUID.fromString(lockId); + } catch (IllegalArgumentException iae) { + return false; + } + + ExecutionContext context = repository.getExecutionContext(); + Graph systemGraph = repository.createSystemGraph(context); + + PathFactory pathFactory = context.getValueFactories().getPathFactory(); + NameFactory nameFactory = context.getValueFactories().getNameFactory(); + + Path lockPath = pathFactory.create(locksPath, nameFactory.create(lockUuid.toString())); + + try { + Node lockNode = systemGraph.getNodeAt(lockPath); + + String workspaceName = firstString(lockNode.getProperty(ModeShapeLexicon.WORKSPACE)); + String rawLockedNodeUuid = firstString(lockNode.getProperty(ModeShapeLexicon.LOCKED_UUID)); + + UUID nodeUuid = UUID.fromString(rawLockedNodeUuid); + + WorkspaceLockManager lockManager = lockManagers.get(workspaceName); + assert lockManager != null; + + ModeShapeLock lock = lockManager.lockFor(nodeUuid); + + // It's possible that the lock was unlocked between when the graph node was read and now + if (lock == null) return false; + + lockManager.unlock(context, lock); + + return true; + } catch (PathNotFoundException pnfe) { + return false; + } + } + /** * {@inheritDoc} * @@ -238,6 +308,14 @@ class RepositoryLockManager implements JcrSystemObserver { return context.getValueFactories().getStringFactory().create(firstValue); } + private final DateTime firstDate( Property property ) { + if (property == null) return null; + Object firstValue = property.getFirstValue(); + + ExecutionContext context = repository.getExecutionContext(); + return context.getValueFactories().getDateFactory().create(firstValue); + } + private final UUID firstUuid( Property property ) { if (property == null) return null; Object firstValue = property.getFirstValue();