[dna-commits] DNA SVN: r1295 - in trunk: dna-cnd/src/test/resources/cnd and 4 other directories.

dna-commits at lists.jboss.org dna-commits at lists.jboss.org
Mon Oct 19 15:54:55 EDT 2009


Author: bcarothers
Date: 2009-10-19 15:54:55 -0400 (Mon, 19 Oct 2009)
New Revision: 1295

Added:
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
Modified:
   trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java
   trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java
   trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
   trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
   trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
   trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java
   trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
   trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java
Log:
DNA-457 Add JSR-170 Locking Optional Feature

Committed patch that provides a locking implementation based on tracking the UUIDs of locked nodes.  This implies that any lockable node must also be referenceable.  The patch allows all of the TCK locking tests to pass as well as a few L2 tests which assumed a locking implementation for the repository.  However, the locking approach does not yet provide any way of integrating the lock/unlock capabilities with the underlying repository.  I will leave the defect open until that need can be addressed as well.

Thanks much for the reviews and suggestions!



Modified: trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java
===================================================================
--- trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-cnd/src/test/java/org/jboss/dna/cnd/CndImporterTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -306,7 +306,7 @@
         // mixin
         // - jcr:lockOwner (string) protected ignore
         // - jcr:lockIsDeep (boolean) protected ignore
-        assertNodeType("mix:lockable", NO_SUPERTYPES, NO_PRIMARY_NAME, NodeOptions.Mixin);
+        assertNodeType("mix:lockable", new String[] {"mix:referenceable"}, NO_PRIMARY_NAME, NodeOptions.Mixin);
         assertProperty("mix:lockable", "jcr:lockOwner", "String", NO_DEFAULTS, OnParentVersion.Ignore, PropertyOptions.Protected);
         assertProperty("mix:lockable",
                        "jcr:lockIsDeep",

Modified: trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd
===================================================================
--- trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-cnd/src/test/resources/cnd/jcr-builtins-170.cnd	2009-10-19 19:54:55 UTC (rev 1295)
@@ -37,7 +37,7 @@
   mixin
   - jcr:uuid (string) mandatory autocreated protected initialize
 
-[mix:lockable]
+[mix:lockable] > mix:referenceable
   mixin
   - jcr:lockOwner (string) protected ignore
   - jcr:lockIsDeep (boolean) protected ignore

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -824,8 +824,8 @@
         JcrNodeType mixinCandidateType = cache.nodeTypes().getNodeType(mixinName);
 
         // Check this separately since it throws a different type of exception
-        if (this.isLocked()) {
-            throw new LockException();
+        if (this.isLocked() && !holdsLock()) {
+            throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
         }
 
         if (!canAddMixin(mixinName)) {
@@ -853,11 +853,10 @@
      */
     public final void removeMixin( String mixinName ) throws RepositoryException {
 
-        if (this.isLocked()) {
-            throw new LockException();
+        if (this.isLocked() && !holdsLock()) {
+            throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
         }
 
-        // TODO: Check access control when that support is added
         // TODO: Throw VersionException if this node is versionable and checked in or unversionable and the nearest versionable
         // ancestor is checked in
 
@@ -1007,6 +1006,12 @@
                                    UUID desiredUuid )
         throws ItemExistsException, PathNotFoundException, VersionException, ConstraintViolationException, LockException,
         RepositoryException {
+
+
+        if (isLocked() && !holdsLock()) {
+            throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
+        }
+        
         // Determine the path ...
         NodeEditor editor = null;
         Path path = null;
@@ -1331,8 +1336,10 @@
      * @return <code>false</code>
      * @see javax.jcr.Node#holdsLock()
      */
-    public final boolean holdsLock() {
-        return false;
+    public final boolean holdsLock() throws RepositoryException {
+        WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lockFor(this.location);
+
+        return lock != null && cache.session().lockTokens().contains(lock.getLockToken());
     }
 
     /**
@@ -1341,41 +1348,99 @@
      * @return <code>false</code>
      * @see javax.jcr.Node#isLocked()
      */
-    public final boolean isLocked() {
-        return false;
+    public final boolean isLocked() throws LockException, RepositoryException {
+        return lock() != null;
     }
-
+    
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedRepositoryOperationException always
      * @see javax.jcr.Node#lock(boolean, boolean)
      */
     public final Lock lock( boolean isDeep,
-                            boolean isSessionScoped ) throws UnsupportedRepositoryOperationException {
-        throw new UnsupportedRepositoryOperationException();
+                            boolean isSessionScoped ) throws LockException, RepositoryException {
+        if (isLocked()) {
+            throw new LockException(JcrI18n.alreadyLocked.text(this.location));
+        }
+
+        if (isDeep) {
+            LinkedList<Node<JcrNodePayload, JcrPropertyPayload>> nodesToVisit = new LinkedList<Node<JcrNodePayload, JcrPropertyPayload>>();
+            nodesToVisit.add(nodeInfo());
+
+            while (!nodesToVisit.isEmpty()) {
+                Node<JcrNodePayload, JcrPropertyPayload> node = nodesToVisit.pop();
+                if (session().workspace().lockManager().lockFor(node.getLocation()) != null) throw new LockException(
+                                                                                                                     JcrI18n.parentAlreadyLocked.text(this.location,
+                                                                                                                                                      node.getLocation()));
+
+                for (Node<JcrNodePayload, JcrPropertyPayload> child : node.getChildren()) {
+                    nodesToVisit.add(child);
+                }
+            }
+        }
+
+        WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lock(cache,
+                                                                                     this.location,
+                                                                                     session().getUserID(),
+                                                                                     isDeep,
+                                                                                     isSessionScoped);
+
+        cache.session().addLockToken(lock.getLockToken());
+        return lock.lockFor(cache);
     }
 
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedRepositoryOperationException always
      * @see javax.jcr.Node#unlock()
      */
-    public final void unlock() throws UnsupportedRepositoryOperationException {
-        throw new UnsupportedRepositoryOperationException();
+    public final void unlock() throws LockException, RepositoryException {
+        WorkspaceLockManager.DnaLock lock = session().workspace().lockManager().lockFor(this.location);
+
+        if (lock == null) {
+            throw new LockException(JcrI18n.notLocked.text(this.location));
+        }
+
+        if (!cache.session().lockTokens().contains(lock.getLockToken())) {
+            throw new LockException(JcrI18n.lockTokenNotHeld.text(this.location));
+        }
+
+        session().workspace().lockManager().unlock(lock);
+        session().removeLockToken(lock.getLockToken());
     }
 
+    private final WorkspaceLockManager.DnaLock lock() throws RepositoryException {
+        // This can only happen in mocked testing.
+        if (session() == null || session().workspace() == null) return null;
+
+        WorkspaceLockManager lockManager = session().workspace().lockManager();
+        WorkspaceLockManager.DnaLock lock = lockManager.lockFor(this.location);
+        if (lock != null) return lock;
+
+        AbstractJcrNode parent = this;
+        while (!parent.isRoot()) {
+            parent = parent.getParent();
+
+            WorkspaceLockManager.DnaLock parentLock = lockManager.lockFor(parent.location);
+            if (parentLock != null && parentLock.isLive()) {
+                return parentLock.isDeep() ? parentLock : null;
+            }
+        }
+        return null;
+    }
+
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedRepositoryOperationException always
      * @see javax.jcr.Node#getLock()
      */
-    public final Lock getLock() throws UnsupportedRepositoryOperationException {
-        throw new UnsupportedRepositoryOperationException();
+    public final Lock getLock() throws LockException, RepositoryException {
+        WorkspaceLockManager.DnaLock lock = lock();
+
+        if (lock == null) throw new LockException(JcrI18n.notLocked.text(this.location));
+        return lock.lockFor(cache);
     }
-
+    
     /**
      * {@inheritDoc}
      * 

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrProperty.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -31,6 +31,7 @@
 import javax.jcr.PathNotFoundException;
 import javax.jcr.Property;
 import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.PropertyDefinition;
@@ -69,6 +70,27 @@
     abstract boolean isMultiple();
 
     /**
+     * Checks that this property's parent node is not already locked by another session. If the parent node is not locked or the
+     * parent node is locked but the lock is owned by this {@code Session}, this method completes silently. If the parent node is
+     * locked (either directly or as part of a deep lock from an ancestor), this method throws a {@code LockException}.
+     * 
+     * @throws LockException if the parent node of this property is locked (that is, if {@code getParent().isLocked() == true &&
+     *         getParent().getLock().getLockToken() == null}.
+     * @throws RepositoryException if any other error occurs
+     * @see Node#isLocked()
+     * @see Lock#getLockToken()
+     */
+    protected final void checkForLock() throws LockException, RepositoryException {
+
+        if (this.getParent().isLocked()) {
+            Lock parentLock = this.getParent().getLock();
+            if (parentLock != null && parentLock.getLockToken() == null) {
+                throw new LockException(JcrI18n.lockTokenNotHeld.text(this.getParent().location));
+            }
+        }
+    }
+
+    /**
      * {@inheritDoc}
      * 
      * @throws IllegalArgumentException if <code>visitor</code> is <code>null</code>.
@@ -145,7 +167,7 @@
      * 
      * @see javax.jcr.Item#getParent()
      */
-    public final Node getParent() {
+    public final AbstractJcrNode getParent() {
         return node;
     }
 

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/DnaLexicon.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -34,9 +34,15 @@
 public class DnaLexicon extends org.jboss.dna.repository.DnaLexicon {
 
     public static final Name BASE = new BasicName(Namespace.URI, "base");
+    public static final Name IS_HELD_BY_SESSION = new BasicName(Namespace.URI, "isHeldBySession");
+    public static final Name IS_SESSION_SCOPED = new BasicName(Namespace.URI, "isSessionScoped");    
+    public static final Name LOCK = new BasicName(Namespace.URI, "lock");    
+    public static final Name LOCKED_NODE = new BasicName(Namespace.URI, "lockedNode");    
+    public static final Name LOCKS = new BasicName(Namespace.URI, "locks");    
     public static final Name NAMESPACE = new BasicName(Namespace.URI, "namespace");
     public static final Name NODE_TYPES = new BasicName(Namespace.URI, "nodeTypes");
     public static final Name REPOSITORIES = new BasicName(Namespace.URI, "repositories");
     public static final Name SYSTEM = new BasicName(Namespace.URI, "system");
     public static final Name URI = new BasicName(Namespace.URI, "uri");
+    public static final Name WORKSPACE = new BasicName(Namespace.URI, "workspace");    
 }

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrI18n.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -166,6 +166,15 @@
     public static I18n cannotAddMixin;
     public static I18n invalidMixinTypeForNode;
     public static I18n notOrderable;
+    
+    // Lock messages
+    public static I18n cannotRemoveLockToken;
+    public static I18n alreadyLocked;
+    public static I18n parentAlreadyLocked;
+    public static I18n notLocked;
+    public static I18n lockTokenNotHeld;
+    public static I18n lockTokenAlreadyHeld;
+    public static I18n uuidRequiredForLock;
 
     static {
         try {

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrMultiValueProperty.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -187,7 +187,8 @@
      */
     public final void setValue( Value[] values )
         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
-
+        checkForLock();
+        
         if (values == null) {
             this.remove();
             return;
@@ -208,6 +209,8 @@
      */
     public final void setValue( String[] values )
         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
+        checkForLock();
+        
         if (values == null) {
             this.remove();
             return;

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrNode.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -26,6 +26,8 @@
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.Node;
 import javax.jcr.RepositoryException;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
 import net.jcip.annotations.NotThreadSafe;
 import org.jboss.dna.graph.Location;
 import org.jboss.dna.graph.session.GraphSession.NodeId;
@@ -96,7 +98,15 @@
      * 
      * @see javax.jcr.Item#remove()
      */
-    public void remove() throws RepositoryException {
+    public void remove() throws RepositoryException, LockException {
+        Node parentNode = getParent();
+        if (parentNode.isLocked()) {
+            Lock parentLock = parentNode.getLock();
+            if (parentLock != null && parentLock.getLockToken() == null) {
+                throw new LockException();
+            }
+        }
+
         editor().destroy();
     }
 }

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrRepository.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -35,6 +35,8 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
 import javax.jcr.Credentials;
 import javax.jcr.NoSuchWorkspaceException;
 import javax.jcr.Repository;
@@ -45,6 +47,7 @@
 import javax.security.auth.login.Configuration;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
+import net.jcip.annotations.GuardedBy;
 import net.jcip.annotations.Immutable;
 import net.jcip.annotations.ThreadSafe;
 import org.jboss.dna.common.i18n.I18n;
@@ -204,6 +207,9 @@
     private final ExecutionContext executionContext;
     private final RepositoryConnectionFactory connectionFactory;
     private final RepositoryNodeTypeManager repositoryTypeManager;
+    @GuardedBy( "lockManagersLock" )
+    private final ConcurrentMap<String, WorkspaceLockManager> lockManagers;
+    private final Path locksPath;
     private final Map<Option, String> options;
     private final String systemSourceName;
     private final String systemWorkspaceName;
@@ -257,7 +263,7 @@
         // Initialize required JCR descriptors.
         modifiableDescriptors.put(Repository.LEVEL_1_SUPPORTED, "true");
         modifiableDescriptors.put(Repository.LEVEL_2_SUPPORTED, "true");
-        modifiableDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, "false");
+        modifiableDescriptors.put(Repository.OPTION_LOCKING_SUPPORTED, "true");
         modifiableDescriptors.put(Repository.OPTION_OBSERVATION_SUPPORTED, "false");
         modifiableDescriptors.put(Repository.OPTION_QUERY_SQL_SUPPORTED, "false");
         modifiableDescriptors.put(Repository.OPTION_TRANSACTIONS_SUPPORTED, "false");
@@ -402,6 +408,9 @@
         this.federatedSource = new FederatedRepositorySource();
         this.federatedSource.setName("JCR " + repositorySourceName);
         this.federatedSource.initialize(new FederatedRepositoryContext(this.connectionFactory));
+
+        this.lockManagers = new ConcurrentHashMap<String, WorkspaceLockManager>();
+        this.locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, DnaLexicon.LOCKS);    
     }
 
     protected void initializeSystemContent( Graph systemGraph ) {
@@ -662,6 +671,24 @@
     }
 
     /**
+     * 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;
+    }
+
+    /**
      * Returns the name of this repository
      * 
      * @return the name of this repository

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSession.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -28,6 +28,8 @@
 import java.io.OutputStream;
 import java.security.AccessControlException;
 import java.util.Calendar;
+import java.util.Collection;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
 import java.util.UUID;
@@ -48,6 +50,7 @@
 import javax.jcr.ValueFactory;
 import javax.jcr.ValueFormatException;
 import javax.jcr.Workspace;
+import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import net.jcip.annotations.Immutable;
 import net.jcip.annotations.NotThreadSafe;
@@ -68,6 +71,7 @@
 import org.jboss.dna.jcr.JcrContentHandler.SaveMode;
 import org.jboss.dna.jcr.JcrNamespaceRegistry.Behavior;
 import org.jboss.dna.jcr.SessionCache.JcrPropertyPayload;
+import org.jboss.dna.jcr.WorkspaceLockManager.DnaLock;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -128,6 +132,8 @@
 
     private final SessionCache cache;
 
+    private final Set<String> lockTokens;
+
     /**
      * A cached instance of the root path.
      */
@@ -159,6 +165,7 @@
 
         this.cache = new SessionCache(this);
         this.isLive = true;
+        this.lockTokens = new HashSet<String>();
 
         assert this.sessionAttributes != null;
         assert this.workspace != null;
@@ -194,6 +201,10 @@
         return this.repository;
     }
 
+    final Collection<String> lockTokens() {
+        return lockTokens;
+    }
+    
     Graph.Batch createBatch() {
         return graph.batch();
     }
@@ -286,11 +297,22 @@
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#addLockToken(java.lang.String)
      */
-    public void addLockToken( String lt ) {
-        throw new UnsupportedOperationException();
+    public void addLockToken( String lt ) throws LockException {
+        CheckArg.isNotNull(lt, "lock token");
+
+        // Trivial case of giving a token back to ourself
+        if (lockTokens.contains(lt)) {
+            return;
+        }
+        
+        if (workspace().lockManager().isHeldBySession(lt)) {
+            throw new LockException(JcrI18n.lockTokenAlreadyHeld.text(lt));
+        }
+        
+        workspace().lockManager().setHeldBySession(lt, true);
+        lockTokens.add(lt);
     }
 
     /**
@@ -493,11 +515,10 @@
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#getLockTokens()
      */
     public String[] getLockTokens() {
-        throw new UnsupportedOperationException();
+        return lockTokens.toArray(new String[lockTokens.size()]);
     }
 
     /**
@@ -751,6 +772,7 @@
             return;
         }
 
+        this.workspace().lockManager().cleanLocks(lockTokens);
         this.executionContext.getSecurityContext().logout();
         isLive = false;
     }
@@ -782,6 +804,20 @@
             throw new ItemExistsException(JcrI18n.childNodeAlreadyExists.text(newNodeNameAsString, newParentNode.getPath()));
         }
 
+        if (sourceNode.isLocked()) {
+            javax.jcr.lock.Lock sourceLock = sourceNode.getLock();
+            if (sourceLock != null && sourceLock.getLockToken() == null) {
+                throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
+            }
+        }
+
+        if (newParentNode.isLocked()) {
+            javax.jcr.lock.Lock newParentLock = newParentNode.getLock();
+            if (newParentLock != null && newParentLock.getLockToken() == null) {
+                throw new LockException(JcrI18n.lockTokenNotHeld.text(destAbsPath));
+            }
+        }
+
         newParentNode.editor().moveToBeChild(sourceNode, newNodeName.getName());
     }
 
@@ -797,11 +833,29 @@
     /**
      * {@inheritDoc}
      * 
-     * @throws UnsupportedOperationException always
      * @see javax.jcr.Session#removeLockToken(java.lang.String)
      */
     public void removeLockToken( String lt ) {
-        throw new UnsupportedOperationException();
+        CheckArg.isNotNull(lt, "lock token");
+        // A LockException is thrown if the lock associated with the specified lock token is session-scoped.
+        /*
+         * The JCR API library that we're using diverges from the spec in that it doesn't declare
+         * this method to throw a LockException.  We'll throw a runtime exception for now.
+         */
+
+        DnaLock lock = workspace().lockManager().lockFor(lt);
+        if (lock == null) {
+            // The lock is no longer valid
+            lockTokens.remove(lt);
+            return;
+        }
+        
+        if (lock.isSessionScoped()) {
+            throw new IllegalStateException(JcrI18n.cannotRemoveLockToken.text(lt));
+        }
+
+        workspace().lockManager().setHeldBySession(lt, false);
+        lockTokens.remove(lt);
     }
 
     /**

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrSingleValueProperty.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -197,6 +197,8 @@
     public void setValue( Value value )
         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         JcrValue jcrValue = null;
+        checkForLock();
+        
         if (value instanceof JcrValue) {
             jcrValue = (JcrValue)value;
 
@@ -249,6 +251,9 @@
     protected void setValue( JcrValue jcrValue )
         throws ValueFormatException, VersionException, LockException, ConstraintViolationException, RepositoryException {
         assert jcrValue != null;
+        
+        checkForLock();
+        
         editor().setProperty(name(), jcrValue);
     }
 

Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/JcrWorkspace.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -41,6 +41,7 @@
 import javax.jcr.Session;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Workspace;
+import javax.jcr.lock.Lock;
 import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.ConstraintViolationException;
 import javax.jcr.nodetype.NodeTypeManager;
@@ -62,6 +63,7 @@
 import org.jboss.dna.graph.property.Name;
 import org.jboss.dna.graph.property.Path;
 import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.Property;
 import org.jboss.dna.graph.property.ValueFormatException;
 import org.jboss.dna.graph.request.InvalidWorkspaceException;
 import org.jboss.dna.graph.request.ReadBranchRequest;
@@ -71,6 +73,7 @@
 import org.jboss.dna.jcr.JcrContentHandler.SaveMode;
 import org.jboss.dna.jcr.SessionCache.JcrNodePayload;
 import org.jboss.dna.jcr.SessionCache.JcrPropertyPayload;
+import org.jboss.dna.jcr.WorkspaceLockManager.DnaLock;
 import org.xml.sax.ContentHandler;
 import org.xml.sax.InputSource;
 import org.xml.sax.SAXException;
@@ -128,6 +131,8 @@
      */
     private final JcrQueryManager queryManager;
 
+    private final WorkspaceLockManager lockManager;
+    
     /**
      * The {@link Session} instance that this corresponds with this workspace.
      */
@@ -144,6 +149,7 @@
         assert repository != null;
         this.name = workspaceName;
         this.repository = repository;
+        this.lockManager = repository.getLockManager(workspaceName);
 
         // // Set up the execution context for this workspace, which should use the namespace registry that persists
         // // the namespaces in the graph ...
@@ -204,6 +210,10 @@
         return this.context;
     }
 
+    final WorkspaceLockManager lockManager() {
+        return this.lockManager;
+    }
+    
     /**
      * {@inheritDoc}
      */
@@ -315,6 +325,32 @@
             // This also performs the check permission for reading the parent ...
             Name newNodeName = destPath.getLastSegment().getName();
             SessionCache cache = this.session.cache();
+            
+
+            /*
+             * Find the UUID for the source node.  Have to go directly against the graph.
+             */
+            org.jboss.dna.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace).getNodeAt(srcPath);
+            Property uuidProp = sourceNode.getProperty(DnaLexicon.UUID);
+
+            if (uuidProp != null) {
+                UUID sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+
+                DnaLock sourceLock = lockManager().lockFor(Location.create(sourceUuid));
+                if (sourceLock != null && sourceLock.getLockToken() == null) {
+                    throw new LockException(JcrI18n.lockTokenNotHeld.text(srcAbsPath));
+                }
+            }
+
+            AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+            if (parentNode.isLocked()) {
+                Lock newParentLock = parentNode.getLock();
+                if (newParentLock != null && newParentLock.getLockToken() == null) {
+                    throw new LockException(destAbsPath);
+                }
+            }
+            
             Node<JcrNodePayload, JcrPropertyPayload> parent = cache.findNode(null, destPath.getParent());
             cache.findBestNodeDefinition(parent, newNodeName, parent.getPayload().getPrimaryTypeName());
 
@@ -445,6 +481,32 @@
             // This also performs the check permission for reading the parent ...
             Name newNodeName = destPath.getLastSegment().getName();
             SessionCache cache = this.session.cache();
+            
+
+            /*
+             * Find the UUID for the source node.  Have to go directly against the graph.
+             */
+            org.jboss.dna.graph.Node sourceNode = repository.createWorkspaceGraph(srcWorkspace).getNodeAt(srcPath);
+            Property uuidProp = sourceNode.getProperty(DnaLexicon.UUID);
+
+            if (uuidProp != null) {
+                UUID sourceUuid = this.context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+
+                DnaLock sourceLock = lockManager().lockFor(Location.create(sourceUuid));
+                if (sourceLock != null && sourceLock.getLockToken() == null) {
+                    throw new LockException(srcAbsPath);
+                }
+            }
+
+            AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+            if (parentNode.isLocked()) {
+                Lock newParentLock = parentNode.getLock();
+                if (newParentLock != null && newParentLock.getLockToken() == null) {
+                    throw new LockException(destAbsPath);
+                }
+            }
+            
             Node<JcrNodePayload, JcrPropertyPayload> parent = cache.findNode(null, destPath.getParent());
             cache.findBestNodeDefinition(parent, newNodeName, parent.getPayload().getPrimaryTypeName());
 
@@ -555,10 +617,32 @@
             Node<JcrNodePayload, JcrPropertyPayload> newParent = cache.findNode(null, destPath.getParent());
             cache.findBestNodeDefinition(newParent, newNodeName, newParent.getPayload().getPrimaryTypeName());
 
+            AbstractJcrNode sourceNode = cache.findJcrNode(Location.create(srcPath));
+
+            if (sourceNode.isLocked()) {
+                Lock sourceLock = sourceNode.getLock();
+                if (sourceLock != null && sourceLock.getLockToken() == null) {
+                    throw new LockException(srcAbsPath);
+                }
+            }
+
+            AbstractJcrNode parentNode = cache.findJcrNode(Location.create(destPath.getParent()));
+
+            if (parentNode.isLocked()) {
+                Lock newParentLock = parentNode.getLock();
+                if (newParentLock != null && newParentLock.getLockToken() == null) {
+                    throw new LockException(destAbsPath);
+                }
+            }            
+
             // Now perform the clone, using the direct (non-session) method ...
             cache.graphSession().immediateMove(srcPath, destPath);
         } catch (AccessControlException ace) {
             throw new AccessDeniedException(ace);
+        } catch (ItemNotFoundException infe) {
+            throw new PathNotFoundException(infe);
+        } catch (org.jboss.dna.graph.property.PathNotFoundException pnfe) {
+            throw new PathNotFoundException(pnfe);
         }
 
         // /*

Added: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java	                        (rev 0)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -0,0 +1,314 @@
+package org.jboss.dna.jcr;
+
+import java.util.Collection;
+import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+import javax.jcr.Node;
+import javax.jcr.PropertyType;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.lock.Lock;
+import javax.jcr.lock.LockException;
+import net.jcip.annotations.ThreadSafe;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
+import org.jboss.dna.graph.property.PathNotFoundException;
+import org.jboss.dna.graph.property.Property;
+import org.jboss.dna.graph.property.PropertyFactory;
+import org.jboss.dna.graph.property.ValueFactory;
+import org.jboss.dna.jcr.SessionCache.NodeEditor;
+
+/**
+ * Manages the locks for a particular workspace in a repository. Locks are stored in a {@code Map<UUID, DnaLock>} while they exist
+ * and are discarded after they are discarded (through the {@link Node#unlock()} method).
+ */
+ at ThreadSafe
+class WorkspaceLockManager {
+
+    private final ExecutionContext context;
+    private final Path locksPath;
+    private final JcrRepository repository;
+    private final String workspaceName;
+    private final ConcurrentMap<UUID, DnaLock> workspaceLocksByNodeUuid;
+
+    WorkspaceLockManager( ExecutionContext context,
+                          JcrRepository repository,
+                          String workspaceName,
+                          Path locksPath ) {
+        this.context = context;
+        this.repository = repository;
+        this.workspaceName = workspaceName;
+        this.locksPath = locksPath;
+
+        this.workspaceLocksByNodeUuid = new ConcurrentHashMap<UUID, DnaLock>();
+
+        Property locksPrimaryType = context.getPropertyFactory().create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.LOCKS);
+        repository.createSystemGraph().create(locksPath, locksPrimaryType).ifAbsent().and();
+    }
+
+    /**
+     * Creates a lock on the node with the given {@link Location}. This method creates a new lock, registers it in the list of
+     * locks, immediately modifies the {@code jcr:lockOwner} and {@code jcr:lockIsDeep} properties on the node in the underlying
+     * repository, and adds the lock to the system view.
+     * <p>
+     * The location given in {@code nodeLocation} must have a UUID.
+     * </p>
+     * 
+     * @param cache the session cache from which the node was loaded
+     * @param nodeLocation the location for the node; may not be null and must have a UUID
+     * @param lockOwner the owner of the new lock
+     * @param isDeep whether the node's descendants in the content graph should also be locked
+     * @param isSessionScoped whether the lock should outlive the session in which it was created
+     * @return an object representing the newly created lock
+     * @throws RepositoryException if an error occurs updating the graph state
+     */
+    DnaLock lock( SessionCache cache,
+                  Location nodeLocation,
+                  String lockOwner,
+                  boolean isDeep,
+                  boolean isSessionScoped ) throws RepositoryException {
+        assert nodeLocation != null;
+        
+        UUID lockUuid = UUID.randomUUID();
+        UUID nodeUuid = uuidFor(nodeLocation);
+
+        if (nodeUuid == null) {
+            throw new RepositoryException(JcrI18n.uuidRequiredForLock.text(nodeLocation));
+        }
+        
+        DnaLock lock = new DnaLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped);
+
+        Graph.Batch batch = repository.createSystemGraph().batch();
+
+        PropertyFactory propFactory = context.getPropertyFactory();
+        PathFactory pathFactory = context.getValueFactories().getPathFactory();
+        Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner);
+        Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep);
+
+        batch.create(pathFactory.create(locksPath, pathFactory.createSegment(lockUuid.toString())),
+                     propFactory.create(JcrLexicon.PRIMARY_TYPE, DnaLexicon.LOCK),
+                     propFactory.create(JcrLexicon.UUID, nodeUuid.toString()),
+                     propFactory.create(DnaLexicon.WORKSPACE, workspaceName),
+                     propFactory.create(DnaLexicon.LOCKED_NODE, nodeUuid.toString()),
+                     propFactory.create(DnaLexicon.IS_SESSION_SCOPED, isSessionScoped),
+                  // This gets set after the lock succeeds and the lock token gets added to the session
+                     propFactory.create(DnaLexicon.IS_HELD_BY_SESSION, false),  
+                     lockOwnerProp,
+                     lockIsDeepProp).ifAbsent().and();
+        batch.execute();
+
+        AbstractJcrNode lockedNode = cache.findJcrNode(Location.create(nodeUuid));
+        NodeEditor editor = cache.getEditorFor(lockedNode.nodeInfo());
+
+        // Set the properties in the cache...
+        editor.setProperty(JcrLexicon.LOCK_OWNER,
+                           (JcrValue)cache.session().getValueFactory().createValue(lockOwner, PropertyType.STRING),
+                           false);
+        editor.setProperty(JcrLexicon.LOCK_IS_DEEP, (JcrValue)cache.session().getValueFactory().createValue(isDeep), false);
+
+        // Write them directly to the underlying graph
+        repository.createWorkspaceGraph(workspaceName).set(lockOwnerProp, lockIsDeepProp).on(nodeUuid);
+
+        workspaceLocksByNodeUuid.put(nodeUuid, lock);
+
+        return lock;
+    }
+
+    /**
+     * Removes the provided lock, effectively unlocking the node to which the lock is associated.
+     * 
+     * @param lock the lock to be removed
+     */
+    void unlock( DnaLock lock ) {
+        try {
+            PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+            Graph.Batch batch = repository.createSystemGraph().batch();
+
+            batch.delete(pathFactory.create(locksPath, pathFactory.createSegment(lock.lockUuid.toString())));
+            batch.remove(JcrLexicon.LOCK_OWNER, JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid);
+            batch.execute();
+            workspaceLocksByNodeUuid.remove(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
+             * owner logs out.  At that point, the lock owner still holds the token for the lock, so it will get cleaned up with a call to cleanLocks.
+             */
+            if (!lock.nodeUuid.equals(pnfe.getLocation().getUuid())) {
+                // If the lock node under dna:locks is not found, this is an internal error
+                throw new IllegalStateException(pnfe);
+            }
+            workspaceLocksByNodeUuid.remove(lock.nodeUuid);
+
+        }
+    }
+
+    boolean isHeldBySession( String lockToken ) {
+        ValueFactory<Boolean> booleanFactory = context.getValueFactories().getBooleanFactory();
+        PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+        org.jboss.dna.graph.Node lockNode = repository.createSystemGraph().getNodeAt(pathFactory.create(locksPath,
+                                                                                        pathFactory.createSegment(lockToken)));
+
+        return booleanFactory.create(lockNode.getProperty(DnaLexicon.IS_HELD_BY_SESSION).getFirstValue());
+        
+    }
+    
+    void setHeldBySession( String lockToken,
+                           boolean value ) {
+        PropertyFactory propFactory = context.getPropertyFactory();
+        PathFactory pathFactory = context.getValueFactories().getPathFactory();
+
+        repository.createSystemGraph().set(propFactory.create(DnaLexicon.IS_HELD_BY_SESSION, value)).on(pathFactory.create(locksPath,
+                                                                                                                           pathFactory.createSegment(lockToken)));
+    }
+    
+    /**
+     * Returns the lock that corresponds to the given lock token
+     * 
+     * @param lockToken the lock token
+     * @return the corresponding lock, possibly null
+     * @see Session#addLockToken(String)
+     * @see Session#removeLockToken(String)
+     */
+    DnaLock lockFor( String lockToken ) {
+        for (DnaLock lock : workspaceLocksByNodeUuid.values()) {
+            if (lockToken.equals(lock.getLockToken())) {
+                return lock;
+            }
+        }
+
+        return null;
+    }
+
+    /**
+     * Returns the lock that corresponds to the given UUID
+     * 
+     * @param nodeUuid the node UUID
+     * @return the corresponding lock, possibly null
+     */
+    DnaLock lockFor( Location nodeLocation ) throws RepositoryException {
+        UUID nodeUuid = uuidFor(nodeLocation);
+        if (nodeUuid == null) return null;
+        return workspaceLocksByNodeUuid.get(nodeUuid);
+    }
+    
+
+    UUID uuidFor( Location location ) throws RepositoryException {
+        if (location.getUuid() != null) return location.getUuid();
+
+        org.jboss.dna.graph.property.Property uuidProp = location.getIdProperty(JcrLexicon.UUID);
+        if (uuidProp == null) return null;
+
+        return context.getValueFactories().getUuidFactory().create(uuidProp.getFirstValue());
+    }
+
+    /**
+     * Unlocks all locks corresponding to the tokens in the {@code lockTokens} collection that are session scoped.
+     * 
+     * @param lockTokens the collection of lock tokens
+     */
+    void cleanLocks( Collection<String> lockTokens ) {
+        for (String lockToken : lockTokens) {
+            DnaLock lock = lockFor(lockToken);
+            if (lock != null && lock.isSessionScoped()) {
+                unlock(lock);
+            }
+        }
+    }
+
+    /**
+     * Internal representation of a locked node. This class should only be created through calls to
+     * {@link WorkspaceLockManager#lock(SessionCache, Location, String, boolean, boolean)}.
+     */
+    @ThreadSafe
+    public class DnaLock {
+        final UUID nodeUuid;
+        private final UUID lockUuid;
+        private final String lockOwner;
+        private final boolean deep;
+        private final boolean sessionScoped;
+
+        DnaLock( String lockOwner,
+                 UUID lockUuid,
+                 UUID nodeUuid,
+                 boolean deep,
+                 boolean sessionScoped ) {
+            super();
+            this.lockOwner = lockOwner;
+            this.lockUuid = lockUuid;
+            this.nodeUuid = nodeUuid;
+            this.deep = deep;
+            this.sessionScoped = sessionScoped;
+        }
+
+        public boolean isLive() {
+            return workspaceLocksByNodeUuid.containsKey(nodeUuid);
+        }
+
+        public boolean isDeep() {
+            return deep;
+        }
+
+        public String getLockOwner() {
+            return lockOwner;
+        }
+
+        public boolean isSessionScoped() {
+            return sessionScoped;
+        }
+
+        public String getLockToken() {
+            return lockUuid.toString();
+        }
+
+        public Lock lockFor( SessionCache cache ) throws RepositoryException {
+            final AbstractJcrNode node = cache.findJcrNode(Location.create(nodeUuid));
+            final JcrSession session = cache.session();
+            return new Lock() {
+                @Override
+                public String getLockOwner() {
+                    return lockOwner;
+                }
+
+                @Override
+                public String getLockToken() {
+                    String uuidString = lockUuid.toString();
+                    return session.lockTokens().contains(uuidString) ? uuidString : null;
+                }
+
+                @Override
+                public Node getNode() {
+                    return node;
+                }
+
+                @Override
+                public boolean isDeep() {
+                    return deep;
+                }
+
+                @Override
+                public boolean isLive() throws RepositoryException {
+                    return workspaceLocksByNodeUuid.containsKey(nodeUuid);
+                }
+
+                @Override
+                public boolean isSessionScoped() {
+                    return sessionScoped;
+                }
+
+                @Override
+                public void refresh() throws LockException, RepositoryException {
+                    if (getLockToken() == null) {
+                        throw new LockException(JcrI18n.notLocked.text(node.location));
+                    }
+                }
+            };
+        }
+
+    }
+}


Property changes on: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
___________________________________________________________________
Name: svn:keywords
   + Id Revision
Name: svn:eol-style
   + LF

Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/JcrI18n.properties	2009-10-19 19:54:55 UTC (rev 1295)
@@ -150,3 +150,12 @@
 cannotAddMixin = This node does not allow adding the mixin type "{0}"
 invalidMixinTypeForNode = "{0}" is not currently a mixin type for node "{1}"
 notOrderable = The primary type "{0}" for this node (at "{1}") does not have orderable children
+
+# Lock messages
+cannotRemoveLockToken = The lock token '{0}' is a session-scoped lock
+alreadyLocked = The node at location '{0}' is already locked
+parentAlreadyLocked = The node at location '{0}' cannot be locked because the parent node at location '{1}' is already locked
+notLocked = The node at location '{0}' is not locked
+lockTokenNotHeld = The node at location '{0}' is locked and this session does not hold its lock token
+lockTokenAlreadyHeld = The lock token '{0}' is already held by another session.  It must be removed from that session before it can be added to another session.
+uuidRequiredForLock = Only referenceable nodes can be locked.  The node at location '(0}' is not referenceable.

Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/dna_builtins.cnd	2009-10-19 19:54:55 UTC (rev 1295)
@@ -43,8 +43,21 @@
 [dna:nodeTypes] > nt:base
 + * (nt:nodeType) = nt:nodeType protected version
 
+[dna:lock] > nt:base
+- dna:lockedNode (string) protected ignore
+- jcr:lockOwner (string) protected ignore
+- jcr:uuid (string) protected ignore
+- dna:sessionScope (boolean) protected ignore
+- jcr:isDeep (boolean) protected ignore
+- dna:isHeldBySession (boolean) protected ignore
+- dna:workspace (string) protected ignore
+
+[dna:locks] > nt:base
++ * (dna:lock) = dna:lock protected ignore
+
 [dna:system] > nt:base
 + dna:namespaces (dna:namespaces) = dna:namespaces autocreated mandatory protected version
++ dna:locks (dna:locks) = dna:locks autocreated mandatory protected ignore
 + jcr:nodeTypes (dna:nodeTypes) = dna:nodeTypes autocreated mandatory protected version
 
 [dna:root] > nt:base, mix:referenceable orderable

Modified: trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd
===================================================================
--- trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/main/resources/org/jboss/dna/jcr/jsr_170_builtins.cnd	2009-10-19 19:54:55 UTC (rev 1295)
@@ -98,6 +98,6 @@
 - jcr:statement (string)
 - jcr:language (string) 
 
-[mix:lockable] mixin 
+[mix:lockable] > mix:referenceable mixin 
 - jcr:lockOwner (string) protected ignore 
 - jcr:lockIsDeep (boolean) protected ignore

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractJcrNodeTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -39,6 +39,7 @@
 import javax.jcr.Repository;
 import javax.jcr.UnsupportedRepositoryOperationException;
 import javax.jcr.Workspace;
+import javax.jcr.lock.LockException;
 import javax.jcr.nodetype.NodeType;
 import javax.jcr.version.Version;
 import org.jboss.dna.graph.Graph;
@@ -402,17 +403,12 @@
         hybrid.getBaseVersion();
     }
 
-    @Test( expected = UnsupportedRepositoryOperationException.class )
-    public void shouldNotAllowGetLock() throws Exception {
+    @Test( expected = LockException.class )
+    public void shouldNotAllowGetLockIfNoLock() throws Exception {
         hybrid.getLock();
     }
 
     @Test
-    public void shouldNotAllowHoldsLock() throws Exception {
-        assertThat(hybrid.holdsLock(), is(false));
-    }
-
-    @Test
     public void shouldNotAllowIsCheckedOut() throws Exception {
         assertThat(hybrid.isCheckedOut(), is(false));
     }
@@ -422,10 +418,11 @@
         assertThat(hybrid.isLocked(), is(false));
     }
 
-    @Test( expected = UnsupportedRepositoryOperationException.class )
-    public void shouldNotAllowLock() throws Exception {
-        hybrid.lock(false, false);
-    }
+    // Now tested in TCK
+    // @Test( expected = UnsupportedRepositoryOperationException.class )
+    // public void shouldAllowLock() throws Exception {
+    // hybrid.lock(false, false);
+    // }
 
     @Test( expected = UnsupportedOperationException.class )
     public void shouldNotAllowMerge() throws Exception {
@@ -457,10 +454,11 @@
         hybrid.restoreByLabel(null, false);
     }
 
-    @Test( expected = UnsupportedRepositoryOperationException.class )
-    public void shouldNotAllowUnlock() throws Exception {
-        hybrid.unlock();
-    }
+    // Now tested in TCK
+    // @Test( expected = UnsupportedRepositoryOperationException.class )
+    // public void shouldNotAllowUnlock() throws Exception {
+    // hybrid.unlock();
+    // }
 
     /*
      * Primary-type and -item methods

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/AbstractSessionTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -41,6 +41,7 @@
 import org.jboss.dna.graph.connector.RepositorySourceException;
 import org.jboss.dna.graph.connector.inmemory.InMemoryRepositorySource;
 import org.jboss.dna.graph.property.NamespaceRegistry;
+import org.jboss.dna.graph.property.Path;
 import org.jboss.dna.graph.property.PathFactory;
 import org.jboss.dna.jcr.nodetype.NodeTypeTemplate;
 import org.mockito.MockitoAnnotations;
@@ -66,6 +67,7 @@
     protected Map<String, Object> sessionAttributes;
     protected Map<JcrRepository.Option, String> options;
     protected NamespaceRegistry registry;
+    protected WorkspaceLockManager workspaceLockManager;
     @Mock
     protected JcrRepository repository;
 
@@ -140,7 +142,21 @@
                 return graph;
             }
         });
+        stub(repository.createSystemGraph()).toAnswer(new Answer<Graph>() {
+            public Graph answer( InvocationOnMock invocation ) throws Throwable {
+                return graph;
+            }
+        });
 
+        Path locksPath = pathFactory.createAbsolutePath(JcrLexicon.SYSTEM, DnaLexicon.LOCKS);
+        workspaceLockManager = new WorkspaceLockManager(context, repository, workspaceName, locksPath);
+
+        stub(repository.getLockManager(anyString())).toAnswer(new Answer<WorkspaceLockManager>() {
+            public WorkspaceLockManager answer( InvocationOnMock invocation ) throws Throwable {
+                return workspaceLockManager;
+            }
+        });
+
         initializeOptions();
         stub(repository.getOptions()).toReturn(options);
 
@@ -148,7 +164,7 @@
         // Set up the session attributes ...
         sessionAttributes = new HashMap<String, Object>();
         sessionAttributes.put("attribute1", "value1");
-
+        
         // Now create the workspace ...
         SecurityContext mockSecurityContext = new MockSecurityContext(null,
                                                                       Collections.singleton(JcrSession.DNA_WRITE_PERMISSION));

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrRepositoryTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -432,7 +432,7 @@
     private void testDescriptorValues( Repository repository ) {
         assertThat(repository.getDescriptor(Repository.LEVEL_1_SUPPORTED), is("true"));
         assertThat(repository.getDescriptor(Repository.LEVEL_2_SUPPORTED), is("true"));
-        assertThat(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED), is("false"));
+        assertThat(repository.getDescriptor(Repository.OPTION_LOCKING_SUPPORTED), is("true"));
         assertThat(repository.getDescriptor(Repository.OPTION_OBSERVATION_SUPPORTED), is("false"));
         assertThat(repository.getDescriptor(Repository.OPTION_QUERY_SQL_SUPPORTED), is("false"));
         assertThat(repository.getDescriptor(Repository.OPTION_TRANSACTIONS_SUPPORTED), is("false"));

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrSessionTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -58,6 +58,7 @@
 import javax.security.auth.login.LoginContext;
 import org.jboss.dna.graph.JaasSecurityContext;
 import org.jboss.dna.graph.property.Path;
+import org.jboss.dna.graph.property.PathFactory;
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
@@ -98,7 +99,7 @@
         }
     }
 
-    @Test( expected = UnsupportedOperationException.class )
+    @Test( expected = IllegalArgumentException.class )
     public void shouldNotAllowAddLockToken() throws Exception {
         session.addLockToken(null);
     }
@@ -158,6 +159,9 @@
         sessionAttributes = new HashMap<String, Object>();
 
         // Now create the workspace ...
+        PathFactory pathFactory = context.getValueFactories().getPathFactory();
+        Path locksPath = pathFactory.create(pathFactory.createRootPath(), JcrLexicon.SYSTEM, DnaLexicon.LOCKS);
+        WorkspaceLockManager wlm = new WorkspaceLockManager(context, repository, workspaceName, locksPath);
         workspace = new JcrWorkspace(repository, workspaceName, context, sessionAttributes);
 
         // Create the session and log in ...
@@ -394,7 +398,7 @@
     @Test( expected = UnsupportedRepositoryOperationException.class )
     public void shouldNotProvideUuidIfNotReferenceable() throws Exception {
         // The b node was not set up to be referenceable in this test, but does have a mixin type
-        Node node = session.getRootNode().getNode("a");
+        Node node = session.getRootNode().getNode("a").getNode("b").getNode("c");
 
         node.getUUID();
     }

Modified: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/JcrTckTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -64,7 +64,9 @@
 import org.apache.jackrabbit.test.api.SetValueValueFormatExceptionTest;
 import org.apache.jackrabbit.test.api.SetValueVersionExceptionTest;
 import org.apache.jackrabbit.test.api.ValueFactoryTest;
+import org.apache.jackrabbit.test.api.WorkspaceCloneReferenceableTest;
 import org.apache.jackrabbit.test.api.WorkspaceCloneSameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceCloneTest;
 import org.apache.jackrabbit.test.api.WorkspaceCloneVersionableTest;
 import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesReferenceableTest;
 import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesSameNameSibsTest;
@@ -72,9 +74,11 @@
 import org.apache.jackrabbit.test.api.WorkspaceCopyBetweenWorkspacesVersionableTest;
 import org.apache.jackrabbit.test.api.WorkspaceCopyReferenceableTest;
 import org.apache.jackrabbit.test.api.WorkspaceCopySameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceCopyTest;
 import org.apache.jackrabbit.test.api.WorkspaceCopyVersionableTest;
 import org.apache.jackrabbit.test.api.WorkspaceMoveReferenceableTest;
 import org.apache.jackrabbit.test.api.WorkspaceMoveSameNameSibsTest;
+import org.apache.jackrabbit.test.api.WorkspaceMoveTest;
 import org.apache.jackrabbit.test.api.WorkspaceMoveVersionableTest;
 
 /**
@@ -230,9 +234,9 @@
             addTestSuite(NodeCanAddMixinTest.class);
             addTestSuite(NodeRemoveMixinTest.class);
 
-            // DNA-518 addTestSuite(WorkspaceCloneReferenceableTest.class);
+            addTestSuite(WorkspaceCloneReferenceableTest.class);
             addTestSuite(WorkspaceCloneSameNameSibsTest.class);
-            // addTestSuite(WorkspaceCloneTest.class);
+            addTestSuite(WorkspaceCloneTest.class);
             addTestSuite(WorkspaceCloneVersionableTest.class);
             addTestSuite(WorkspaceCopyBetweenWorkspacesReferenceableTest.class);
             addTestSuite(WorkspaceCopyBetweenWorkspacesSameNameSibsTest.class);
@@ -240,11 +244,11 @@
             addTestSuite(WorkspaceCopyBetweenWorkspacesVersionableTest.class);
             addTestSuite(WorkspaceCopyReferenceableTest.class);
             addTestSuite(WorkspaceCopySameNameSibsTest.class);
-            // addTestSuite(WorkspaceCopyTest.class);
+            addTestSuite(WorkspaceCopyTest.class);
             addTestSuite(WorkspaceCopyVersionableTest.class);
             addTestSuite(WorkspaceMoveReferenceableTest.class);
             addTestSuite(WorkspaceMoveSameNameSibsTest.class);
-            // addTestSuite(WorkspaceMoveTest.class);
+            addTestSuite(WorkspaceMoveTest.class);
             addTestSuite(WorkspaceMoveVersionableTest.class);
 
             addTestSuite(RepositoryLoginTest.class);
@@ -269,7 +273,7 @@
 
             // addTest(org.apache.jackrabbit.test.api.observation.TestAll.suite());
             // addTest(org.apache.jackrabbit.test.api.version.TestAll.suite());
-            // addTest(org.apache.jackrabbit.test.api.lock.TestAll.suite());
+            addTest(org.apache.jackrabbit.test.api.lock.TestAll.suite());
             addTest(org.apache.jackrabbit.test.api.util.TestAll.suite());
         }
     }

Modified: trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java
===================================================================
--- trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java	2009-10-15 19:57:20 UTC (rev 1294)
+++ trunk/web/dna-web-jcr-rest-war/src/test/java/org/jboss/dna/web/jcr/rest/JcrResourcesTest.java	2009-10-19 19:54:55 UTC (rev 1295)
@@ -236,8 +236,9 @@
         assertThat(properties.getString("jcr:primaryType"), is("dna:system"));
 
         JSONArray namespaces = system.getJSONArray("children");
-        assertThat(namespaces.length(), is(1));
+        assertThat(namespaces.length(), is(2));
         assertThat(namespaces.getString(0), is("dna:namespaces"));
+        assertThat(namespaces.getString(1), is("dna:locks"));
 
         assertThat(connection.getResponseCode(), is(HttpURLConnection.HTTP_OK));
         connection.disconnect();
@@ -260,10 +261,12 @@
         assertThat(properties.getString("jcr:primaryType"), is("dna:system"));
 
         JSONObject children = body.getJSONObject("children");
-        assertThat(children.length(), is(1));
+        assertThat(children.length(), is(2));
 
         JSONObject namespaces = children.getJSONObject("dna:namespaces");
         assertThat(namespaces.length(), is(2));
+        JSONObject locks = children.getJSONObject("dna:locks");
+        assertThat(locks.length(), is(1));
 
         properties = namespaces.getJSONObject("properties");
         assertThat(properties.length(), is(1));



More information about the dna-commits mailing list