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).
+ */
+@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));