Author: bcarothers
Date: 2009-10-21 18:12:10 -0400 (Wed, 21 Oct 2009)
New Revision: 1304
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/LockFailedException.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/LockBranchRequest.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/UnlockBranchRequest.java
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/RepositorySourceCapabilities.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRepository.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapWorkspace.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/LoggingRequestProcessor.java
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java
trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
trunk/dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/MockRepositoryRequestProcessor.java
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/test/WritableConnectorTest.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java
trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
trunk/extensions/dna-connector-infinispan/src/main/java/org/jboss/dna/connector/infinispan/InfinispanRepository.java
trunk/extensions/dna-connector-jbosscache/src/main/java/org/jboss/dna/connector/jbosscache/JBossCacheRepository.java
Log:
DNA-457 Add JSR-170 Locking Optional Feature
Committed patch (DNA-457_repo_locks.patch) that adds comments to address the previous
feedback and provides a graph-layer API for locking nodes in repositories that provide
their own locking mechanism (e.g., SVN). The patch also modifies the JCR locking code to
propagate JCR locks to the graph layer.
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-10-21 17:29:30 UTC
(rev 1303)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/Graph.java 2009-10-21 22:12:10 UTC
(rev 1304)
@@ -406,6 +406,165 @@
}
/**
+ * Request to lock the specified node. This request is submitted to the repository
immediately.
+ *
+ * @param at the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( Node at )
{
+ return lock(at.getLocation());
+ }
+
+ /**
+ * Request to lock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param atPath the path of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( String
atPath ) {
+ return lock(Location.create(createPath(atPath)));
+ }
+
+ /**
+ * Request to lock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param at the path of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( Path at )
{
+ return lock(Location.create(at));
+ }
+
+ /**
+ * Request to lock the node with the given UUID. This request is submitted to the
repository immediately.
+ *
+ * @param at the UUID of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( UUID at )
{
+ return lock(Location.create(at));
+ }
+
+ /**
+ * Request to lock the node with the given unique identification property. This
request is submitted to the repository
+ * immediately.
+ *
+ * @param idProperty the unique identifying property of the node that is to be
locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( Property
idProperty ) {
+ return lock(Location.create(idProperty));
+ }
+
+ /**
+ * Request to lock the node with the given identification properties. The
identification properties should uniquely identify a
+ * single node. This request is submitted to the repository immediately.
+ *
+ * @param firstIdProperty the first identification property of the node that is to be
copied
+ * @param additionalIdProperties the remaining identification properties of the node
that is to be copied
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( Property
firstIdProperty,
+ Property... additionalIdProperties ) {
+ return lock(Location.create(firstIdProperty, additionalIdProperties));
+ }
+
+ /**
+ * Request to lock the node at the given location. This request is submitted to the
repository immediately.
+ *
+ * @param at the location of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<Conjunction<Graph>>> lock( Location at
) {
+ return new LockAction<Conjunction<Graph>>(this.nextGraph, at) {
+ @Override
+ protected Conjunction<Graph> submit( Location target,
+
org.jboss.dna.graph.request.LockBranchRequest.LockScope lockScope,
+ long lockTimeoutInMillis ) {
+ String workspaceName = getCurrentWorkspaceName();
+ requests.lockBranch(workspaceName, target, lockScope,
lockTimeoutInMillis);
+ return and();
+ }
+ };
+ }
+
+ /**
+ * Request to unlock the specified node. This request is submitted to the repository
immediately.
+ *
+ * @param at the node that is to be unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( Node at ) {
+ return unlock(at.getLocation());
+ }
+
+ /**
+ * Request to unlock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param atPath the path of the node that is to be unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( String atPath ) {
+ return unlock(Location.create(createPath(atPath)));
+ }
+
+ /**
+ * Request to unlock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param at the path of the node that is to be unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( Path at ) {
+ return unlock(Location.create(at));
+ }
+
+ /**
+ * Request to unlock the node with the given UUID. This request is submitted to the
repository immediately.
+ *
+ * @param at the UUID of the node that is to be unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( UUID at ) {
+ return unlock(Location.create(at));
+ }
+
+ /**
+ * Request to unlock the node with the given unique identification property. This
request is submitted to the repository
+ * immediately.
+ *
+ * @param idProperty the unique identifying property of the node that is to be
unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( Property idProperty ) {
+ return unlock(Location.create(idProperty));
+ }
+
+ /**
+ * Request to unlock the node with the given identification properties. The
identification properties should uniquely identify
+ * a single node. This request is submitted to the repository immediately.
+ *
+ * @param firstIdProperty the first identification property of the node that is to be
copied
+ * @param additionalIdProperties the remaining identification properties of the node
that is to be copied
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( Property firstIdProperty,
+ Property... additionalIdProperties ) {
+ return unlock(Location.create(firstIdProperty, additionalIdProperties));
+ }
+
+ /**
+ * Request to unlock the node at the given location. This request is submitted to the
repository immediately.
+ *
+ * @param at the location of the node that is to be unlocked
+ * @return an object that may be used to start another request
+ */
+ public Conjunction<Graph> unlock( Location at ) {
+ String workspaceName = getCurrentWorkspaceName();
+ requests.unlockBranch(workspaceName, at);
+ return this.nextGraph;
+ }
+
+ /**
* Begin the request to move the specified node into a parent node at a different
location, which is specified via the
* <code>into(...)</code> method on the returned {@link Move} object.
* <p>
@@ -1847,15 +2006,13 @@
}
public List<Location> under( Location at ) {
- return requests.readBlockOfChildren(at,
getCurrentWorkspaceName(), startingIndex, blockSize)
- .getChildren();
+ return requests.readBlockOfChildren(at,
getCurrentWorkspaceName(), startingIndex, blockSize).getChildren();
}
};
}
public List<Location> startingAfter( final Location
previousSibling ) {
- return requests.readNextBlockOfChildren(previousSibling,
getCurrentWorkspaceName(), blockSize)
- .getChildren();
+ return requests.readNextBlockOfChildren(previousSibling,
getCurrentWorkspaceName(), blockSize).getChildren();
}
public List<Location> startingAfter( String
pathOfPreviousSibling ) {
@@ -2450,6 +2607,164 @@
}
/**
+ * Request to lock the specified node. This request is submitted to the
repository immediately.
+ *
+ * @param at the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( Node at ) {
+ return lock(at.getLocation());
+ }
+
+ /**
+ * Request to lock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param atPath the path of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( String atPath )
{
+ return lock(Location.create(createPath(atPath)));
+ }
+
+ /**
+ * Request to lock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param at the path of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( Path at ) {
+ return lock(Location.create(at));
+ }
+
+ /**
+ * Request to lock the node with the given UUID. This request is submitted to the
repository immediately.
+ *
+ * @param at the UUID of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( UUID at ) {
+ return lock(Location.create(at));
+ }
+
+ /**
+ * Request to lock the node with the given unique identification property. This
request is submitted to the repository
+ * immediately.
+ *
+ * @param idProperty the unique identifying property of the node that is to be
locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( Property
idProperty ) {
+ return lock(Location.create(idProperty));
+ }
+
+ /**
+ * Request to lock the node with the given identification properties. The
identification properties should uniquely
+ * identify a single node. This request is submitted to the repository
immediately.
+ *
+ * @param firstIdProperty the first identification property of the node that is
to be copied
+ * @param additionalIdProperties the remaining identification properties of the
node that is to be copied
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( Property
firstIdProperty,
+ Property... additionalIdProperties ) {
+ return lock(Location.create(firstIdProperty, additionalIdProperties));
+ }
+
+ /**
+ * Request to lock the node at the given location. This request is submitted to
the repository immediately.
+ *
+ * @param at the location of the node that is to be locked
+ * @return an object that allows the scope of the lock to be defined
+ */
+ public LockScope<LockTimeout<BatchConjunction>> lock( Location at )
{
+ return new LockAction<BatchConjunction>(this.nextRequests, at) {
+ @Override
+ protected BatchConjunction submit( Location target,
+
org.jboss.dna.graph.request.LockBranchRequest.LockScope lockScope,
+ long lockTimeoutInMillis ) {
+ String workspaceName = getCurrentWorkspaceName();
+ requests.lockBranch(workspaceName, target, lockScope,
lockTimeoutInMillis);
+ return and();
+ }
+ };
+ }
+
+ /**
+ * Request to unlock the specified node. This request is submitted to the
repository immediately.
+ *
+ * @param at the node that is to be unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( Node at ) {
+ return unlock(at.getLocation());
+ }
+
+ /**
+ * Request to unlock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param atPath the path of the node that is to be unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( String atPath ) {
+ return unlock(Location.create(createPath(atPath)));
+ }
+
+ /**
+ * Request to unlock the node at the given path. This request is submitted to the
repository immediately.
+ *
+ * @param at the path of the node that is to be unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( Path at ) {
+ return unlock(Location.create(at));
+ }
+
+ /**
+ * Request to unlock the node with the given UUID. This request is submitted to
the repository immediately.
+ *
+ * @param at the UUID of the node that is to be unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( UUID at ) {
+ return unlock(Location.create(at));
+ }
+
+ /**
+ * Request to unlock the node with the given unique identification property. This
request is submitted to the repository
+ * immediately.
+ *
+ * @param idProperty the unique identifying property of the node that is to be
unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( Property idProperty ) {
+ return unlock(Location.create(idProperty));
+ }
+
+ /**
+ * Request to unlock the node with the given identification properties. The
identification properties should uniquely
+ * identify a single node. This request is submitted to the repository
immediately.
+ *
+ * @param firstIdProperty the first identification property of the node that is
to be copied
+ * @param additionalIdProperties the remaining identification properties of the
node that is to be copied
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( Property firstIdProperty,
+ Property... additionalIdProperties ) {
+ return unlock(Location.create(firstIdProperty, additionalIdProperties));
+ }
+
+ /**
+ * Request to unlock the node at the given location. This request is submitted to
the repository immediately.
+ *
+ * @param at the location of the node that is to be unlocked
+ * @return the interface that can either execute the batched requests or continue
to add additional requests to the batch
+ */
+ public BatchConjunction unlock( Location at ) {
+ requests.unlockBranch(workspaceName, at);
+ return this.nextRequests;
+ }
+
+ /**
* Begin the request to clone the specified node into a parent node at a
different location, which is specified via the
* <code>into(...)</code> method on the returned {@link Clone}
object.
* <p>
@@ -4390,6 +4705,78 @@
}
/**
+ * Interface for specifying whether a lock should be deep in scope
+ *
+ * @param <Next> The interface that is to be returned when this create request
is completed
+ */
+ public interface LockScope<Next> {
+ Next andItsDescendants();
+
+ Next only();
+ }
+
+ /**
+ * Interface for specifying whether the maximum length of the lock
+ *
+ * @param <Next> The interface that is to be returned when this create request
is completed
+ */
+ public interface LockTimeout<Next> {
+ Next withDefaultTimeout();
+
+ Next withTimeoutOf( long milliseconds );
+ }
+
+ @NotThreadSafe
+ protected abstract class LockAction<T> extends AbstractAction<T>
implements LockScope<LockTimeout<T>> {
+ private final Location target;
+
+ /*package*/LockAction( T afterConjunction,
+ Location target ) {
+ super(afterConjunction);
+ this.target = target;
+ }
+
+ protected abstract T submit( Location lock,
+
org.jboss.dna.graph.request.LockBranchRequest.LockScope lockScope,
+ long lockTimeoutInMillis );
+
+ public LockTimeout<T> andItsDescendants() {
+ return new LockTimeout<T>() {
+
+ @Override
+ public T withDefaultTimeout() {
+ return submit(target,
org.jboss.dna.graph.request.LockBranchRequest.LockScope.SELF_AND_DESCENDANTS, 0);
+ }
+
+ @Override
+ public T withTimeoutOf( long milliseconds ) {
+ return submit(target,
+
org.jboss.dna.graph.request.LockBranchRequest.LockScope.SELF_AND_DESCENDANTS,
+ milliseconds);
+ }
+
+ };
+ }
+
+ public LockTimeout<T> only() {
+ return new LockTimeout<T>() {
+
+ @Override
+ public T withDefaultTimeout() {
+ return submit(target,
org.jboss.dna.graph.request.LockBranchRequest.LockScope.SELF_ONLY, 0);
+ }
+
+ @Override
+ public T withTimeoutOf( long milliseconds ) {
+ return submit(target,
org.jboss.dna.graph.request.LockBranchRequest.LockScope.SELF_ONLY, milliseconds);
+ }
+
+ };
+ }
+
+ }
+
+ /**
* The interface for defining additional properties on a new node.
*
* @param <Next> The interface that is to be returned when this create request
is completed
@@ -6041,10 +6428,7 @@
}
public SubgraphNode getNode( Name relativePath ) {
- Path path = getGraph().getContext()
- .getValueFactories()
- .getPathFactory()
- .create(getLocation().getPath(), relativePath);
+ Path path =
getGraph().getContext().getValueFactories().getPathFactory().create(getLocation().getPath(),
relativePath);
path = path.getNormalizedPath();
return getNode(path);
}
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-10-21 17:29:30
UTC (rev 1303)
+++ trunk/dna-graph/src/main/java/org/jboss/dna/graph/GraphI18n.java 2009-10-21 22:12:10
UTC (rev 1304)
@@ -89,6 +89,7 @@
public static I18n errorImportingContent;
public static I18n unableToFindRepositorySourceWithName;
public static I18n nodeAlreadyExistsWithUuid;
+ public static I18n couldNotAcquireLock;
/* In-Memory Connector */
public static I18n inMemoryNodeDoesNotExist;
Added:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/LockFailedException.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/LockFailedException.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/LockFailedException.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -0,0 +1,46 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.connector;
+
+import net.jcip.annotations.Immutable;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+
+/**
+ * Exception that indicates that a lock request failed at the repository level. This may
be due to a conflict with an existing
+ * lock, an internal timeout that occurred while attempting to lock the target, a lack of
permissions, or any other reason.
+ */
+@Immutable
+public class LockFailedException extends RepositorySourceException {
+
+ private static final long serialVersionUID = 1L;
+
+ public LockFailedException( String repositorySourceName,
+ Location target,
+ String workspaceName,
+ Throwable cause ) {
+ super(repositorySourceName, GraphI18n.couldNotAcquireLock.text(target,
workspaceName), cause);
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/LockFailedException.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/RepositorySourceCapabilities.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/RepositorySourceCapabilities.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/RepositorySourceCapabilities.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -59,24 +59,30 @@
*/
public static final boolean DEFAULT_SUPPORT_REFERENCES = true;
- private boolean sameNameSiblings;
- private boolean updates;
- private boolean events;
- private boolean creatingWorkspaces;
- private boolean references;
+ /**
+ * The default support for creating locks is {@value} .
+ */
+ public static final boolean DEFAULT_SUPPORT_LOCKS = false;
+ private final boolean sameNameSiblings;
+ private final boolean updates;
+ private final boolean events;
+ private final boolean creatingWorkspaces;
+ private final boolean references;
+ private final boolean locks;
+
/**
* Create a capabilities object using the defaults, .
*/
public RepositorySourceCapabilities() {
this(DEFAULT_SUPPORT_SAME_NAME_SIBLINGS, DEFAULT_SUPPORT_UPDATES,
DEFAULT_SUPPORT_EVENTS,
- DEFAULT_SUPPORT_CREATING_WORKSPACES, DEFAULT_SUPPORT_REFERENCES);
+ DEFAULT_SUPPORT_CREATING_WORKSPACES, DEFAULT_SUPPORT_REFERENCES,
DEFAULT_SUPPORT_LOCKS);
}
public RepositorySourceCapabilities( boolean supportsSameNameSiblings,
boolean supportsUpdates ) {
this(supportsSameNameSiblings, supportsUpdates, DEFAULT_SUPPORT_EVENTS,
DEFAULT_SUPPORT_CREATING_WORKSPACES,
- DEFAULT_SUPPORT_REFERENCES);
+ DEFAULT_SUPPORT_REFERENCES, DEFAULT_SUPPORT_LOCKS);
}
public RepositorySourceCapabilities( boolean supportsSameNameSiblings,
@@ -84,11 +90,24 @@
boolean supportsEvents,
boolean supportsCreatingWorkspaces,
boolean supportsReferences ) {
+ this(supportsSameNameSiblings, supportsUpdates, supportsEvents,
supportsCreatingWorkspaces, supportsReferences,
+ DEFAULT_SUPPORT_LOCKS);
+
+ }
+
+ public RepositorySourceCapabilities( boolean supportsSameNameSiblings,
+ boolean supportsUpdates,
+ boolean supportsEvents,
+ boolean supportsCreatingWorkspaces,
+ boolean supportsReferences,
+ boolean supportsLocks ) {
+
this.sameNameSiblings = supportsSameNameSiblings;
this.updates = supportsUpdates;
this.events = supportsEvents;
this.creatingWorkspaces = supportsCreatingWorkspaces;
this.references = supportsReferences;
+ this.locks = supportsLocks;
}
/**
@@ -121,6 +140,19 @@
}
/**
+ * Return whether the source supports locking nodes.
+ * <p>
+ * Sources that support locking nodes must be able to explicitly lock and unlock
nodes in a manner that is persistent and
+ * stable across repository connections. Sources that cannot provide this capability
should return false from this method.
+ * </p>
+ *
+ * @return true if locks are supported, or false otherwise
+ */
+ public boolean supportsLocks() {
+ return locks;
+ }
+
+ /**
* Return whether the source supports publishing change events.
*
* @return true if events are supported, or false if the source is not capable of
generating events
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/inmemory/InMemoryRepository.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -29,11 +29,13 @@
import net.jcip.annotations.GuardedBy;
import net.jcip.annotations.NotThreadSafe;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.connector.LockFailedException;
import org.jboss.dna.graph.connector.map.AbstractMapWorkspace;
import org.jboss.dna.graph.connector.map.MapNode;
import org.jboss.dna.graph.connector.map.MapRepository;
import org.jboss.dna.graph.connector.map.MapWorkspace;
import org.jboss.dna.graph.property.Name;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
/**
* A specialized {@link MapRepository} that represents an in-memory repository.
@@ -124,6 +126,18 @@
return super.copyNode(context, original, newWorkspace, newParent,
desiredName, recursive, oldToNewUuids);
}
+ @Override
+ public void lockNode( MapNode node,
+ LockScope lockScope,
+ long lockTimeoutInMillis ) throws LockFailedException {
+ // Locking is not supported by this connector
+ }
+
+ @Override
+ public void unlockNode( MapNode node ) {
+ // Locking is not supported by this connector
+ }
+
/**
* Method added to support testing
*
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRepository.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRepository.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRepository.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -266,5 +266,4 @@
public boolean destroyWorkspace( String name ) {
return workspaces.remove(name) != null;
}
-
}
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapRequestProcessor.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -54,10 +54,12 @@
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
import org.jboss.dna.graph.request.InvalidWorkspaceException;
+import org.jboss.dna.graph.request.LockBranchRequest;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
import org.jboss.dna.graph.request.Request;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
import org.jboss.dna.graph.request.processor.RequestProcessor;
@@ -103,6 +105,32 @@
}
@Override
+ public void process( LockBranchRequest request ) {
+ MapWorkspace workspace = getWorkspace(request, request.inWorkspace());
+ MapNode node = getTargetNode(workspace, request, request.at());
+ if (node == null) return;
+
+ workspace.lockNode(node, request.lockScope(), request.lockTimeoutInMillis());
+
+ Location actualLocation = getActualLocation(request.at(), node);
+ request.setActualLocation(actualLocation);
+ recordChange(request);
+ }
+
+ @Override
+ public void process( UnlockBranchRequest request ) {
+ MapWorkspace workspace = getWorkspace(request, request.inWorkspace());
+ MapNode node = getTargetNode(workspace, request, request.at());
+ if (node == null) return;
+
+ workspace.unlockNode(node);
+
+ Location actualLocation = getActualLocation(request.at(), node);
+ request.setActualLocation(actualLocation);
+ recordChange(request);
+ }
+
+ @Override
public void process( ReadAllPropertiesRequest request ) {
MapWorkspace workspace = getWorkspace(request, request.inWorkspace());
MapNode node = getTargetNode(workspace, request, request.at());
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapWorkspace.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapWorkspace.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/connector/map/MapWorkspace.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -27,10 +27,12 @@
import java.util.UUID;
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.LockFailedException;
import org.jboss.dna.graph.connector.UuidAlreadyExistsException;
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.request.LockBranchRequest.LockScope;
/**
* The {@code MapWorkspace} defines the required methods for workspaces in a {@link
MapRepository map repository}. By default, a
@@ -180,6 +182,27 @@
Set<Location> removedExistingNodes ) throws
UuidAlreadyExistsException;
/**
+ * Attempts to lock the given node with the given timeout. If the lock attempt fails,
a {@link LockFailedException} will be
+ * thrown.
+ *
+ * @param node the node to be locked; may not be null
+ * @param lockScope the scope of the lock (i.e., whether descendants of {@code node}
should be included in the lock
+ * @param lockTimeoutInMillis the maximum lifetime of the lock in milliseconds; zero
(0) indicates that the connector default
+ * should be used
+ * @throws LockFailedException if the implementing connector supports locking but the
lock could not be acquired.
+ */
+ void lockNode( MapNode node,
+ LockScope lockScope,
+ long lockTimeoutInMillis ) throws LockFailedException;
+
+ /**
+ * Attempts to unlock the given node.
+ *
+ * @param node the node to be unlocked; may not be null
+ */
+ void unlockNode( MapNode node );
+
+ /**
* Find the lowest existing node along the path.
*
* @param path the path to the node; may not be null
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/LockBranchRequest.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/LockBranchRequest.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/LockBranchRequest.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -0,0 +1,234 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.request;
+
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * Instruction to lock an existing node or branch. Connectors that do not support locking
(as defined in
+ * {@link RepositorySourceCapabilities#supportsLocks()}) must ignore this request.
+ */
+public class LockBranchRequest extends ChangeRequest {
+
+ private static final long serialVersionUID = 1L;
+
+ public enum LockScope {
+ SELF_ONLY,
+ SELF_AND_DESCENDANTS;
+ }
+
+ private final Location at;
+ private final String workspaceName;
+ private final LockScope isDeep;
+ private final long lockTimeoutInMillis;
+ private Location actualLocation;
+
+ /**
+ * Create a request to lock the node or branch at the supplied location.
+ *
+ * @param at the location of the node to be read
+ * @param workspaceName the name of the workspace containing the node
+ * @param isDeep whether the lock should be deep (i.e., should include all of the
node's descendants)
+ * @param lockTimeoutInMillis the number of milliseconds that the lock should last
before the lock times out; zero (0)
+ * indicates that the connector default should be used
+ * @throws IllegalArgumentException if the location or workspace name is null
+ */
+ public LockBranchRequest( Location at,
+ String workspaceName,
+ LockScope isDeep,
+ long lockTimeoutInMillis ) {
+ CheckArg.isNotNull(at, "at");
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ this.workspaceName = workspaceName;
+ this.at = at;
+ this.isDeep = isDeep;
+ this.lockTimeoutInMillis = lockTimeoutInMillis;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.Request#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ /**
+ * Get the location defining the node that is to be read.
+ *
+ * @return the location of the node; never null
+ */
+ public Location at() {
+ return at;
+ }
+
+ /**
+ * Get the name of the workspace in which the node exists.
+ *
+ * @return the name of the workspace; never null
+ */
+ public String inWorkspace() {
+ return workspaceName;
+ }
+
+ /**
+ * Get whether the lock should include all of the descendants of the node as well as
the node itself
+ *
+ * @return {@link LockScope#SELF_AND_DESCENDANTS} if the lock should include all of
the descendants of the node,
+ * {@link LockScope#SELF_ONLY} otherwise
+ */
+ public LockScope lockScope() {
+ return isDeep;
+ }
+
+ /**
+ * Gets the maximum length of the lock in milliseconds
+ *
+ * @return the number of milliseconds that the lock should last before the lock times
out; zero (0) indicates that the
+ * connector default should be used
+ */
+ public long lockTimeoutInMillis() {
+ return lockTimeoutInMillis;
+ }
+
+ /**
+ * Sets the actual and complete location of the node being locked. This method must
be called when processing the request, and
+ * the actual location must have a {@link Location#getPath() path}.
+ *
+ * @param actualLocation the actual location of the node before being locked
+ * @throws IllegalArgumentException if the either location is null or is missing its
path, if the old location does not
+ * represent the {@link Location#isSame(Location) same location} as the
{@link #at() current location}
+ * @throws IllegalStateException if the request is frozen
+ */
+ public void setActualLocation( Location actualLocation ) {
+ checkNotFrozen();
+ if (!at.isSame(actualLocation)) { // not same if actual is null
+ throw new
IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actualLocation,
at));
+ }
+ assert actualLocation != null;
+ if (!actualLocation.hasPath()) {
+ throw new
IllegalArgumentException(GraphI18n.actualOldLocationMustHavePath.text(actualLocation));
+ }
+ this.actualLocation = actualLocation;
+ }
+
+ /**
+ * Get the actual location of the node that was locked.
+ *
+ * @return the actual location of the node being locked, or null if the actual
location was not set
+ */
+ public Location getActualLocation() {
+ return actualLocation;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changes(java.lang.String,
org.jboss.dna.graph.property.Path)
+ */
+ @Override
+ public boolean changes( String workspace,
+ Path path ) {
+ return this.workspaceName.equals(workspace) && at.hasPath() &&
at.getPath().getParent().isAtOrBelow(path);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changedLocation()
+ */
+ @Override
+ public Location changedLocation() {
+ return at;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changedWorkspace()
+ */
+ @Override
+ public String changedWorkspace() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.Request#cancel()
+ */
+ @Override
+ public void cancel() {
+ super.cancel();
+ this.actualLocation = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return HashCode.compute(at, workspaceName, lockScope(), lockTimeoutInMillis);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (this.getClass().isInstance(obj)) {
+ LockBranchRequest that = (LockBranchRequest)obj;
+ if (this.lockTimeoutInMillis() != that.lockTimeoutInMillis()) return false;
+ if (!this.at().equals(that.at())) return false;
+ if (this.lockScope() != that.lockScope()) return false;
+ if (!this.inWorkspace().equals(that.inWorkspace())) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "lock " + (LockScope.SELF_AND_DESCENDANTS == lockScope() ?
"branch rooted" : "node") + " at " + at()
+ + " in the \"" + workspaceName + "\"
workspace";
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/LockBranchRequest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/RequestBuilder.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -35,6 +35,7 @@
import org.jboss.dna.graph.property.Property;
import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior;
import org.jboss.dna.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
/**
* A component that can be used to build requests while allowing different strategies for
how requests are handled. Subclasses can
@@ -616,4 +617,38 @@
return request;
}
+ /**
+ * Create a request to lock a branch or node
+ *
+ * @param workspaceName the name of the workspace containing the node; may not be
null
+ * @param target the location of the top node in the existing branch that is to be
locked
+ * @param lockScope the {@link LockBranchRequest#lockScope()} scope of the lock
+ * @param lockTimeoutInMillis the number of milliseconds that the lock should last
before the lock times out; zero (0)
+ * indicates that the connector default should be used
+ * @return the request; never null
+ * @throws IllegalArgumentException if any of the parameters are null
+ */
+ public LockBranchRequest lockBranch( String workspaceName,
+ Location target,
+ LockScope lockScope,
+ long lockTimeoutInMillis ) {
+ return process(new LockBranchRequest(target, workspaceName, lockScope,
lockTimeoutInMillis));
+ }
+
+ /**
+ * Create a request to unlock a branch or node
+ * <p>
+ * The lock on the node should be removed. If the lock was deep (i.e., locked the
entire branch under the node, then all of
+ * the descendants of the node affected by the lock should also be unlocked after
this request is processed.
+ * </p>
+ *
+ * @param workspaceName the name of the workspace containing the node; may not be
null
+ * @param target the location of the top node in the existing branch that is to be
unlocked
+ * @return the request; never null
+ * @throws IllegalArgumentException if any of the parameters are null
+ */
+ public UnlockBranchRequest unlockBranch( String workspaceName,
+ Location target ) {
+ return process(new UnlockBranchRequest(target, workspaceName));
+ }
}
Added: trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/UnlockBranchRequest.java
===================================================================
--- trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/UnlockBranchRequest.java
(rev 0)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/UnlockBranchRequest.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -0,0 +1,197 @@
+/*
+ * JBoss DNA (
http://www.jboss.org/dna)
+ * See the COPYRIGHT.txt file distributed with this work for information
+ * regarding copyright ownership. Some portions may be licensed
+ * to Red Hat, Inc. under one or more contributor license agreements.
+ * See the AUTHORS.txt file in the distribution for a full listing of
+ * individual contributors.
+ *
+ * JBoss DNA is free software. Unless otherwise indicated, all code in JBoss DNA
+ * is licensed to you under the terms of the GNU Lesser General Public License as
+ * published by the Free Software Foundation; either version 2.1 of
+ * the License, or (at your option) any later version.
+ *
+ * JBoss DNA is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this software; if not, write to the Free
+ * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
+ * 02110-1301 USA, or see the FSF site:
http://www.fsf.org.
+ */
+package org.jboss.dna.graph.request;
+
+import org.jboss.dna.common.util.CheckArg;
+import org.jboss.dna.common.util.HashCode;
+import org.jboss.dna.graph.GraphI18n;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
+import org.jboss.dna.graph.property.Path;
+
+/**
+ * Instruction to unlock an existing node or branch. Connectors that do not support
locking (as defined in
+ * {@link RepositorySourceCapabilities#supportsLocks()}) must ignore this request.
+ */
+public class UnlockBranchRequest extends ChangeRequest {
+
+ private static final long serialVersionUID = 1L;
+
+ private final Location at;
+ private final String workspaceName;
+ private Location actualLocation;
+
+ /**
+ * Create a request to unlock the node or branch at the supplied location.
+ *
+ * @param at the location of the node to be unlocked
+ * @param workspaceName the name of the workspace containing the node
+ * @throws IllegalArgumentException if the location or workspace name is null
+ */
+ public UnlockBranchRequest( Location at,
+ String workspaceName ) {
+ CheckArg.isNotNull(at, "at");
+ CheckArg.isNotNull(workspaceName, "workspaceName");
+ this.workspaceName = workspaceName;
+ this.at = at;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.Request#isReadOnly()
+ */
+ @Override
+ public boolean isReadOnly() {
+ return false;
+ }
+
+ /**
+ * Get the location defining the node that is to be read.
+ *
+ * @return the location of the node; never null
+ */
+ public Location at() {
+ return at;
+ }
+
+ /**
+ * Get the name of the workspace in which the node exists.
+ *
+ * @return the name of the workspace; never null
+ */
+ public String inWorkspace() {
+ return workspaceName;
+ }
+
+ /**
+ * Sets the actual and complete location of the node being unlocked. This method must
be called when processing the request,
+ * and the actual location must have a {@link Location#getPath() path}.
+ *
+ * @param actualLocation the actual location of the node before being unlocked
+ * @throws IllegalArgumentException if the either location is null or is missing its
path, if the old location does not
+ * represent the {@link Location#isSame(Location) same location} as the
{@link #at() current location}
+ * @throws IllegalStateException if the request is frozen
+ */
+ public void setActualLocation( Location actualLocation ) {
+ checkNotFrozen();
+ if (!at.isSame(actualLocation)) { // not same if actual is null
+ throw new
IllegalArgumentException(GraphI18n.actualLocationIsNotSameAsInputLocation.text(actualLocation,
at));
+ }
+ assert actualLocation != null;
+ if (!actualLocation.hasPath()) {
+ throw new
IllegalArgumentException(GraphI18n.actualOldLocationMustHavePath.text(actualLocation));
+ }
+ this.actualLocation = actualLocation;
+ }
+
+ /**
+ * Get the actual location of the node that was unlocked.
+ *
+ * @return the actual location of the node being unlocked, or null if the actual
location was not set
+ */
+ public Location getActualLocation() {
+ return actualLocation;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changes(java.lang.String,
org.jboss.dna.graph.property.Path)
+ */
+ @Override
+ public boolean changes( String workspace,
+ Path path ) {
+ return this.workspaceName.equals(workspace) && at.hasPath() &&
at.getPath().getParent().isAtOrBelow(path);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changedLocation()
+ */
+ @Override
+ public Location changedLocation() {
+ return at;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.ChangeRequest#changedWorkspace()
+ */
+ @Override
+ public String changedWorkspace() {
+ return workspaceName;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.jboss.dna.graph.request.Request#cancel()
+ */
+ @Override
+ public void cancel() {
+ super.cancel();
+ this.actualLocation = null;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#hashCode()
+ */
+ @Override
+ public int hashCode() {
+ return HashCode.compute(at, workspaceName);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#equals(java.lang.Object)
+ */
+ @Override
+ public boolean equals( Object obj ) {
+ if (obj == this) return true;
+ if (this.getClass().isInstance(obj)) {
+ UnlockBranchRequest that = (UnlockBranchRequest)obj;
+ if (!this.at().equals(that.at())) return false;
+ if (!this.inWorkspace().equals(that.inWorkspace())) return false;
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see java.lang.Object#toString()
+ */
+ @Override
+ public String toString() {
+ return "unlock branch at " + at() + " in the \"" +
workspaceName + "\" workspace";
+ }
+
+}
Property changes on:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/UnlockBranchRequest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/LoggingRequestProcessor.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/LoggingRequestProcessor.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/LoggingRequestProcessor.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -36,6 +36,7 @@
import org.jboss.dna.graph.request.DeleteBranchRequest;
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
+import org.jboss.dna.graph.request.LockBranchRequest;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
@@ -48,6 +49,7 @@
import org.jboss.dna.graph.request.RenameNodeRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.SetPropertyRequest;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyNodeExistsRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
@@ -356,6 +358,30 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.LockBranchRequest)
+ */
+ @Override
+ public void process( LockBranchRequest request ) {
+ logger.log(level, GraphI18n.executingRequest, request);
+ delegate.process(request);
+ logger.log(level, GraphI18n.executedRequest, request);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UnlockBranchRequest)
+ */
+ @Override
+ public void process( UnlockBranchRequest request ) {
+ logger.log(level, GraphI18n.executingRequest, request);
+ delegate.process(request);
+ logger.log(level, GraphI18n.executedRequest, request);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.Request)
*/
@Override
Modified:
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java
===================================================================
---
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/main/java/org/jboss/dna/graph/request/processor/RequestProcessor.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -37,6 +37,8 @@
import org.jboss.dna.graph.GraphI18n;
import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.cache.CachePolicy;
+import org.jboss.dna.graph.connector.LockFailedException;
+import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
import org.jboss.dna.graph.observe.Changes;
import org.jboss.dna.graph.observe.Observer;
import org.jboss.dna.graph.property.DateTime;
@@ -57,6 +59,7 @@
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
import org.jboss.dna.graph.request.InvalidRequestException;
+import org.jboss.dna.graph.request.LockBranchRequest;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
@@ -69,6 +72,7 @@
import org.jboss.dna.graph.request.RenameNodeRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.SetPropertyRequest;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UnsupportedRequestException;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.UpdateValuesRequest;
@@ -251,6 +255,10 @@
process((UpdatePropertiesRequest)request);
} else if (request instanceof VerifyNodeExistsRequest) {
process((VerifyNodeExistsRequest)request);
+ } else if (request instanceof LockBranchRequest) {
+ process((LockBranchRequest)request);
+ } else if (request instanceof UnlockBranchRequest) {
+ process((UnlockBranchRequest)request);
} else if (request instanceof VerifyWorkspaceRequest) {
process((VerifyWorkspaceRequest)request);
} else if (request instanceof GetWorkspacesRequest) {
@@ -779,7 +787,7 @@
Property property = readProperty.getProperty();
List<Object> actualRemovedValues = new
ArrayList<Object>(request.removedValues().size());
List<Object> newValues = property == null ? new LinkedList<Object>()
: new LinkedList<Object>(
-
Arrays.asList(property.getValuesAsArray()));
+
Arrays.asList(property.getValuesAsArray()));
// Calculate what the new values should be
for (Object removedValue : request.removedValues()) {
for (Iterator<Object> iter = newValues.iterator(); iter.hasNext();) {
@@ -793,7 +801,7 @@
newValues.addAll(request.addedValues());
Property newProperty =
getExecutionContext().getPropertyFactory().create(propertyName, newValues);
-
+
// Update the current values
SetPropertyRequest setProperty = new SetPropertyRequest(on, workspaceName,
newProperty);
process(setProperty);
@@ -845,6 +853,35 @@
}
/**
+ * Process a request to lock a node or branch within a workspace
+ * <p>
+ * The default implementation of this method does nothing, as most connectors will
not support locking (as defined in
+ * {@link RepositorySourceCapabilities#supportsLocks()}). Any implementation of this
method should do nothing if the request
+ * is null.
+ * </p>
+ * <p>
+ * Implementations that do support locking should throw a {@link LockFailedException}
if the request could not be fulfilled.
+ * </p>
+ *
+ * @param request the request
+ */
+ public void process( LockBranchRequest request ) {
+ }
+
+ /**
+ * Process a request to unlock a node or branch within a workspace
+ * <p>
+ * The default implementation of this method does nothing, as most connectors will
not support locking (as defined in
+ * {@link RepositorySourceCapabilities#supportsLocks()}). Any implementation of this
method should do nothing if the request
+ * is null.
+ * </p>
+ *
+ * @param request the request
+ */
+ public void process( UnlockBranchRequest request ) {
+ }
+
+ /**
* A class that represents a location at a known depth
*
* @author Randall Hauch
Modified: trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties
===================================================================
--- trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-10-21
17:29:30 UTC (rev 1303)
+++ trunk/dna-graph/src/main/resources/org/jboss/dna/graph/GraphI18n.properties 2009-10-21
22:12:10 UTC (rev 1304)
@@ -77,6 +77,7 @@
errorImportingContent = Error importing {0} content from {1}
unableToFindRepositorySourceWithName = Unable to find a repository source named
"{0}"
nodeAlreadyExistsWithUuid = A node with UUID "{0}" already exists at path
"{1}" in workspace "{2}"
+couldNotAcquireLock = Could not acquire lock on the node at "{0}" in workspace
"{1}"
# In-memory connector
inMemoryNodeDoesNotExist = Could not find an existing node at {0}
Modified: trunk/dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java
===================================================================
--- trunk/dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java 2009-10-21 17:29:30
UTC (rev 1303)
+++ trunk/dna-graph/src/test/java/org/jboss/dna/graph/GraphTest.java 2009-10-21 22:12:10
UTC (rev 1304)
@@ -63,6 +63,7 @@
import org.jboss.dna.graph.request.DeleteBranchRequest;
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
+import org.jboss.dna.graph.request.LockBranchRequest;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
@@ -72,11 +73,13 @@
import org.jboss.dna.graph.request.ReadPropertyRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.SetPropertyRequest;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyNodeExistsRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
import org.jboss.dna.graph.request.CloneWorkspaceRequest.CloneConflictBehavior;
import org.jboss.dna.graph.request.CreateWorkspaceRequest.CreateConflictBehavior;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
import org.jboss.dna.graph.request.processor.RequestProcessor;
import org.junit.Before;
import org.junit.Test;
@@ -206,6 +209,24 @@
assertThat(delete.at(), is(at));
}
+ protected void assertNextRequestIsLock( Location at,
+ LockScope lockScope,
+ long lockTimeout ) {
+ Request request = executedRequests.poll();
+ assertThat(request, is(instanceOf(LockBranchRequest.class)));
+ LockBranchRequest lock = (LockBranchRequest)request;
+ assertThat(lock.at(), is(at));
+ assertThat(lock.lockScope(), is(lockScope));
+ assertThat(lock.lockTimeoutInMillis(), is(lockTimeout));
+ }
+
+ protected void assertNextRequestIsUnlock( Location at ) {
+ Request request = executedRequests.poll();
+ assertThat(request, is(instanceOf(UnlockBranchRequest.class)));
+ UnlockBranchRequest unlock = (UnlockBranchRequest)request;
+ assertThat(unlock.at(), is(at));
+ }
+
protected void assertNextRequestIsCreate( Location parent,
String child,
Property... properties ) {
@@ -1072,6 +1093,24 @@
graph.useWorkspace("something");
}
+ @Test
+ public void shouldLockNodeButNotDescendants() {
+ graph.lock(validPath).only().withDefaultTimeout();
+ assertNextRequestIsLock(Location.create(validPath), LockScope.SELF_ONLY, 0);
+ }
+
+ @Test
+ public void shouldLockNodeAndItsDescendants() {
+ graph.lock(validPath).andItsDescendants().withTimeoutOf(12345);
+ assertNextRequestIsLock(Location.create(validPath),
LockScope.SELF_AND_DESCENDANTS, 12345);
+ }
+
+ @Test
+ public void shouldUnlockNode() {
+ graph.unlock(validPath);
+ assertNextRequestIsUnlock(Location.create(validPath));
+ }
+
//
----------------------------------------------------------------------------------------------------------------
// Implementation of RepositoryConnection and RequestProcessor for tests
//
----------------------------------------------------------------------------------------------------------------
@@ -1136,6 +1175,18 @@
}
@Override
+ public void process( LockBranchRequest request ) {
+ // Just update the actual location
+ request.setActualLocation(actualLocationOf(request.at()));
+ }
+
+ @Override
+ public void process( UnlockBranchRequest request ) {
+ // Just update the actual location
+ request.setActualLocation(actualLocationOf(request.at()));
+ }
+
+ @Override
public void process( MoveBranchRequest request ) {
// Just update the actual location ...
Name newName = request.desiredName();
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/MockRepositoryRequestProcessor.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/MockRepositoryRequestProcessor.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/MockRepositoryRequestProcessor.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -25,6 +25,7 @@
import java.util.Queue;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Location;
import org.jboss.dna.graph.request.CloneBranchRequest;
import org.jboss.dna.graph.request.CloneWorkspaceRequest;
import org.jboss.dna.graph.request.CopyBranchRequest;
@@ -33,6 +34,7 @@
import org.jboss.dna.graph.request.DeleteBranchRequest;
import org.jboss.dna.graph.request.DestroyWorkspaceRequest;
import org.jboss.dna.graph.request.GetWorkspacesRequest;
+import org.jboss.dna.graph.request.LockBranchRequest;
import org.jboss.dna.graph.request.MoveBranchRequest;
import org.jboss.dna.graph.request.ReadAllChildrenRequest;
import org.jboss.dna.graph.request.ReadAllPropertiesRequest;
@@ -45,6 +47,7 @@
import org.jboss.dna.graph.request.RenameNodeRequest;
import org.jboss.dna.graph.request.Request;
import org.jboss.dna.graph.request.SetPropertyRequest;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
import org.jboss.dna.graph.request.UpdatePropertiesRequest;
import org.jboss.dna.graph.request.VerifyNodeExistsRequest;
import org.jboss.dna.graph.request.VerifyWorkspaceRequest;
@@ -55,7 +58,9 @@
*/
public class MockRepositoryRequestProcessor extends RequestProcessor {
- private Queue<Request> processed;
+ private static final String DEFAULT_WORKSPACE_NAME = "default";
+ private final Queue<Request> processed;
+ private final Location defaultWorkspaceRoot;
/**
* @param sourceName
@@ -67,7 +72,9 @@
Queue<Request> processed ) {
super(sourceName, context, null);
assert processed != null;
+
this.processed = processed;
+ this.defaultWorkspaceRoot =
Location.create(context.getValueFactories().getPathFactory().createRootPath());
}
protected void record( Request request ) {
@@ -82,6 +89,10 @@
@Override
public void process( VerifyWorkspaceRequest request ) {
record(request);
+
+ // Need to add this loopback in so we can use this with JCR layer test cases
+ request.setActualWorkspaceName(request.getActualWorkspaceName() == null ?
DEFAULT_WORKSPACE_NAME : request.getActualWorkspaceName());
+ request.setActualRootLocation(request.getActualLocationOfRoot() == null ?
defaultWorkspaceRoot : request.getActualLocationOfRoot());
}
/**
@@ -177,6 +188,26 @@
/**
* {@inheritDoc}
*
+ * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.LockBranchRequest)
+ */
+ @Override
+ public void process( LockBranchRequest request ) {
+ record(request);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.UnlockBranchRequest)
+ */
+ @Override
+ public void process( UnlockBranchRequest request ) {
+ record(request);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
* @see
org.jboss.dna.graph.request.processor.RequestProcessor#process(org.jboss.dna.graph.request.ReadAllChildrenRequest)
*/
@Override
Modified:
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/test/WritableConnectorTest.java
===================================================================
---
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/test/WritableConnectorTest.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/dna-graph/src/test/java/org/jboss/dna/graph/connector/test/WritableConnectorTest.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -83,6 +83,10 @@
super.afterEach();
}
+ private boolean supportsLocks() {
+ return source.getCapabilities().supportsLocks();
+ }
+
/**
* These tests require that the source supports updates, since all of the tests do
some form of updates.
*/
@@ -120,20 +124,9 @@
@Test
public void shouldAddChildrenAndSettingProperties() {
- graph.batch()
- .set("propA")
- .to("valueA")
- .on("/")
- .and()
- .create("/a")
- .with("propB", "valueB")
- .and("propC", "valueC")
- .and()
- .create("/b")
- .with("propD", "valueD")
- .and("propE", "valueE")
- .and()
- .execute();
+
graph.batch().set("propA").to("valueA").on("/").and().create("/a").with("propB",
"valueB").and("propC",
"valueC").and().create("/b").with("propD",
+
"valueD").and("propE",
+
"valueE").and().execute();
// Now look up the root node ...
Node root = graph.getNodeAt("/");
assertThat(root, is(notNullValue()));
@@ -886,20 +879,12 @@
graph.create("/newUuids").and();
// Copy once to get the UUID into the default workspace
//
graph.copy("/node1/node1/node1").failingIfUuidsMatch().fromWorkspace(workspaceName).to("/newUuids/node1");
- graph.clone("/node1/node1/node1")
- .fromWorkspace(workspaceName)
- .as(name("node1"))
- .into("/newUuids")
- .failingIfAnyUuidsMatch();
+
graph.clone("/node1/node1/node1").fromWorkspace(workspaceName).as(name("node1")).into("/newUuids").failingIfAnyUuidsMatch();
try {
// Copy again to get the exception since the UUID is already in the default
workspace
//
graph.copy("/node1/node1").failingIfUuidsMatch().fromWorkspace(workspaceName).to("/newUuids/shouldNotWork");
- graph.clone("/node1/node1/node1")
- .fromWorkspace(workspaceName)
- .as(name("shouldNotWork"))
- .into("/newUuids")
- .failingIfAnyUuidsMatch();
+
graph.clone("/node1/node1/node1").fromWorkspace(workspaceName).as(name("shouldNotWork")).into("/newUuids").failingIfAnyUuidsMatch();
fail("Should not be able to copy a node into a workspace if another node
with the "
+ "same UUID already exists in the workspace and UUID behavior is
failingIfUuidsMatch");
} catch (UuidAlreadyExistsException ex) {
@@ -931,11 +916,7 @@
graph.create("/newUuids").and();
// Copy once to get the UUID into the default workspace
//
graph.copy("/node1").replacingExistingNodesWithSameUuids().fromWorkspace(workspaceName).to("/newUuids/node1");
- graph.clone("/node1")
- .fromWorkspace(workspaceName)
- .as(name("node1"))
- .into("/newUuids")
- .replacingExistingNodesWithSameUuids();
+
graph.clone("/node1").fromWorkspace(workspaceName).as(name("node1")).into("/newUuids").replacingExistingNodesWithSameUuids();
// Make sure that the node wasn't moved by the clone
graph.useWorkspace(workspaceName);
@@ -948,11 +929,7 @@
// Copy again to test the behavior now that the UUIDs are already in the default
workspace
// This should remove /newUuids/node1/shouldBeRemoved
//
graph.copy("/node1").replacingExistingNodesWithSameUuids().fromWorkspace(workspaceName).to("/newUuids/otherNode");
- graph.clone("/node1")
- .fromWorkspace(workspaceName)
- .as(name("otherNode"))
- .into("/newUuids")
- .replacingExistingNodesWithSameUuids();
+
graph.clone("/node1").fromWorkspace(workspaceName).as(name("otherNode")).into("/newUuids").replacingExistingNodesWithSameUuids();
/*
* Focus on testing node structure, since shouldCopyNodeWithChildren tests that
properties get copied
@@ -1009,11 +986,7 @@
// Copy again to test the behavior now that the UUIDs are already in the default
workspace
// This should remove /segmentTestUuids/node1[1]
- graph.clone("/node1")
- .fromWorkspace(workspaceName)
- .as(segment("node1[1]"))
- .into("/segmentTestUuids")
- .replacingExistingNodesWithSameUuids();
+
graph.clone("/node1").fromWorkspace(workspaceName).as(segment("node1[1]")).into("/segmentTestUuids").replacingExistingNodesWithSameUuids();
/*
* Focus on testing node structure, since shouldCopyNodeWithChildren tests that
properties get copied
@@ -1955,4 +1928,25 @@
assertThat(subgraph.getNode("node1"),
hasProperty("property1", "The quick brown fox jumped over the moon. What?
"));
}
+ @Test
+ public void shouldLockNode() {
+ if (!supportsLocks()) return;
+
+ fail("Need to add test body here");
+ }
+
+ @Test
+ public void shouldNotAllowMultipleConcurrentLocksOnSameNode() {
+ if (!supportsLocks()) return;
+
+ fail("Need to add test body here");
+ }
+
+ @Test
+ public void shouldUnlockNode() {
+ if (!supportsLocks()) return;
+
+ fail("Need to add test body here");
+ }
+
}
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-21 17:29:30
UTC (rev 1303)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/AbstractJcrNode.java 2009-10-21 22:12:10
UTC (rev 1304)
@@ -1368,7 +1368,7 @@
nodesToVisit.add(nodeInfo());
while (!nodesToVisit.isEmpty()) {
- Node<JcrNodePayload, JcrPropertyPayload> node =
nodesToVisit.get(nodesToVisit.size() - 1);
+ Node<JcrNodePayload, JcrPropertyPayload> node =
nodesToVisit.remove(nodesToVisit.size() - 1);
if (session().workspace().lockManager().lockFor(node.getLocation()) !=
null) throw new LockException(
JcrI18n.parentAlreadyLocked.text(this.location,
node.getLocation()));
Modified: trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java
===================================================================
--- trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java 2009-10-21
17:29:30 UTC (rev 1303)
+++ trunk/dna-jcr/src/main/java/org/jboss/dna/jcr/WorkspaceLockManager.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -4,6 +4,7 @@
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
@@ -14,6 +15,8 @@
import org.jboss.dna.graph.ExecutionContext;
import org.jboss.dna.graph.Graph;
import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.LockFailedException;
+import org.jboss.dna.graph.connector.RepositorySourceCapabilities;
import org.jboss.dna.graph.property.Path;
import org.jboss.dna.graph.property.PathFactory;
import org.jboss.dna.graph.property.PathNotFoundException;
@@ -80,7 +83,7 @@
throw new
RepositoryException(JcrI18n.uuidRequiredForLock.text(nodeLocation));
}
- DnaLock lock = new DnaLock(lockOwner, lockUuid, nodeUuid, isDeep,
isSessionScoped);
+ DnaLock lock = createLock(lockOwner, lockUuid, nodeUuid, isDeep,
isSessionScoped);
Graph.Batch batch = repository.createSystemGraph().batch();
@@ -110,15 +113,73 @@
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);
-
+ lockNodeInRepository(nodeUuid, lockOwnerProp, lockIsDeepProp, lock, isDeep);
workspaceLocksByNodeUuid.put(nodeUuid, lock);
return lock;
}
+ /* Factory method added to facilitate mocked testing */
+ DnaLock createLock( String lockOwner,
+ UUID lockUuid,
+ UUID nodeUuid,
+ boolean isDeep,
+ boolean isSessionScoped ) {
+ return new DnaLock(lockOwner, lockUuid, nodeUuid, isDeep, isSessionScoped);
+ }
+
/**
+ * Marks the node as locked in the underlying repository with an immediate write
(that is, the write is not part of the JCR
+ * session scope and cannot be "rolled back" with a refresh at the {@link
Session#refresh(boolean) session} or
+ * {@link Item#refresh(boolean) item} level.
+ * <p>
+ * This method will also attempt to {@link Graph#lock(Location) lock the node in the
underlying repository}. If the underlying
+ * repository {@link RepositorySourceCapabilities#supportsLocks() supports locks} and
{@link LockFailedException the lock
+ * attempt fails}, this method will cancel the lock attempt by calling {@link
#unlock(DnaLock)} and will throw a {@code
+ * RepositoryException}.
+ * </p>
+ * <p>
+ * This method does not modify the system graph. In other words, it will not create
the record for the lock in the {@code
+ * /jcr:system/dna:locks} subgraph.
+ * </p>
+ *
+ * @param nodeUuid the UUID of the node to lock
+ * @param lockOwnerProp an existing property with name {@link JcrLexicon#LOCK_OWNER}
and the value being the name of the lock
+ * owner
+ * @param lockIsDeepProp an existing property with name {@link
JcrLexicon#LOCK_IS_DEEP} and the value being {@code true} if
+ * the lock should include all descendants of the locked node or {@code false}
if the lock should only include the
+ * specified node and not its descendants
+ * @param lock the internal lock representation
+ * @param isDeep {@code true} if the lock should include all descendants of the
locked node or {@code false} if the lock
+ * should only include the specified node and not its descendants. This value
is redundant with the lockIsDeep
+ * parameter, but is included separately as a minor performance optimization
+ * @throws RepositoryException if the repository in which the node represented by
{@code nodeUuid} supports locking but
+ * signals that the lock for the node cannot be acquired
+ */
+ void lockNodeInRepository( UUID nodeUuid,
+ Property lockOwnerProp,
+ Property lockIsDeepProp,
+ DnaLock lock,
+ boolean isDeep ) throws RepositoryException {
+ // Write them directly to the underlying graph
+ Graph.Batch workspaceBatch =
repository.createWorkspaceGraph(workspaceName).batch();
+ workspaceBatch.set(lockOwnerProp, lockIsDeepProp).on(nodeUuid);
+ if (isDeep) {
+ workspaceBatch.lock(nodeUuid).andItsDescendants().withDefaultTimeout();
+ } else {
+ workspaceBatch.lock(nodeUuid).only().withDefaultTimeout();
+ }
+ try {
+ workspaceBatch.execute();
+ } catch (LockFailedException lfe) {
+ // Attempt to lock node at the repo level failed - cancel lock
+ unlock(lock);
+ throw new RepositoryException(lfe);
+ }
+
+ }
+
+ /**
* Removes the provided lock, effectively unlocking the node to which the lock is
associated.
*
* @param lock the lock to be removed
@@ -132,6 +193,9 @@
batch.delete(pathFactory.create(locksPath,
pathFactory.createSegment(lock.getUuid().toString())));
batch.remove(JcrLexicon.LOCK_OWNER,
JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid);
batch.execute();
+
+ unlockNodeInRepository(lock);
+
workspaceLocksByNodeUuid.remove(lock.nodeUuid);
} catch (PathNotFoundException pnfe) {
/*
@@ -147,20 +211,58 @@
}
}
+ /**
+ * Removes the workspace record of the lock in the underlying repository. This method
clears the {@code jcr:lockOwner} and
+ * {@code jcr:lockIsDeep} properties on the node and sends an {@link
Graph#unlock(Location) unlock request} to the underlying
+ * repository to clear any locks that it is holding on the node.
+ * <p>
+ * This method does not modify the system graph. In other words, it will not remove
the record for the lock in the {@code
+ * /jcr:system/dna:locks} subgraph.
+ * </p>
+ *
+ * @param lock
+ */
+ void unlockNodeInRepository( DnaLock lock ) {
+ Graph.Batch workspaceBatch =
repository.createWorkspaceGraph(this.workspaceName).batch();
+
+ workspaceBatch.remove(JcrLexicon.LOCK_OWNER,
JcrLexicon.LOCK_IS_DEEP).on(lock.nodeUuid);
+ workspaceBatch.unlock(lock.nodeUuid);
+
+ workspaceBatch.execute();
+ }
+
+ /**
+ * Checks whether the given lock token is currently held by any session by querying
the lock record in the underlying
+ * repository.
+ *
+ * @param lockToken the lock token to check; may not be null
+ * @return true if a session currently holds the lock token, false otherwise
+ */
boolean isHeldBySession( String lockToken ) {
+ assert lockToken != null;
+
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)));
+ 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());
}
+ /**
+ * Updates the underlying repository directly (i.e., outside the scope of the {@link
Session}) to mark the token for the given
+ * lock as being held (or not held) by some {@link Session}. Note that this method
does not identify <i>which</i> (if any)
+ * session holds the token for the lock, just that <i>some</i> session
holds the token for the lock.
+ *
+ * @param lockToken the lock token for which the "held" status should be
modified; may not be null
+ * @param value the new value
+ */
void setHeldBySession( String lockToken,
boolean value ) {
+ assert lockToken != null;
+
PropertyFactory propFactory = context.getPropertyFactory();
PathFactory pathFactory = context.getValueFactories().getPathFactory();
@@ -194,13 +296,23 @@
* @return the corresponding lock, possibly null if there is no such lock
* @throws RepositoryException if there is a problem obtaining the information for a
lock
*/
- DnaLock lockFor( Location nodeLocation ) throws RepositoryException {
+ DnaLock lockFor( Location nodeLocation ) {
UUID nodeUuid = uuidFor(nodeLocation);
if (nodeUuid == null) return null;
return workspaceLocksByNodeUuid.get(nodeUuid);
}
+ /**
+ * Returns the UUID that identifies the given location or {@code null} if the
location does not have a UUID. The method
+ * returns the {@link Location#getUuid() default UUID} if it exists. If it does not,
the method returns the value of the
+ * {@link JcrLexicon#UUID} property as a UUID. If the location does not contain that
property, the method returns null.
+ *
+ * @param location the location for which the UUID should be returned
+ * @return the UUID that identifies the given location or {@code null} if the
location does not have a UUID.
+ */
UUID uuidFor( Location location ) {
+ assert location != null;
+
if (location.getUuid() != null) return location.getUuid();
org.jboss.dna.graph.property.Property uuidProp =
location.getIdProperty(JcrLexicon.UUID);
@@ -303,7 +415,7 @@
return sessionScoped;
}
- public void refresh() throws LockException, RepositoryException {
+ public void refresh() throws LockException {
if (getLockToken() == null) {
throw new LockException(JcrI18n.notLocked.text(node.location));
}
Added: trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
===================================================================
--- trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
(rev 0)
+++ trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -0,0 +1,156 @@
+package org.jboss.dna.jcr;
+
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsInstanceOf.instanceOf;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.stub;
+import java.util.LinkedList;
+import java.util.UUID;
+import javax.jcr.RepositoryException;
+import javax.security.auth.login.LoginException;
+import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.Graph;
+import org.jboss.dna.graph.Location;
+import org.jboss.dna.graph.connector.MockRepositoryConnection;
+import org.jboss.dna.graph.connector.RepositoryConnectionFactory;
+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.PropertyFactory;
+import org.jboss.dna.graph.request.LockBranchRequest;
+import org.jboss.dna.graph.request.Request;
+import org.jboss.dna.graph.request.UnlockBranchRequest;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
+import org.jboss.dna.jcr.WorkspaceLockManager.DnaLock;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.MockitoAnnotations;
+import org.mockito.MockitoAnnotations.Mock;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
+
+public class WorkspaceLockManagerTest {
+
+ private Graph graph;
+ private ExecutionContext context;
+ private UUID validUuid;
+ private Location validLocation;
+ private String sourceName;
+ private String workspaceName;
+ private MockRepositoryConnection connection;
+ private LinkedList<Request> executedRequests;
+
+ private RepositoryNodeTypeManager repoTypeManager;
+ private WorkspaceLockManager workspaceLockManager;
+
+ @Mock
+ private RepositoryConnectionFactory connectionFactory;
+ @Mock
+ protected JcrRepository repository;
+
+ @Before
+ public void beforeEach() throws LoginException {
+ MockitoAnnotations.initMocks(this);
+ executedRequests = new LinkedList<Request>();
+ sourceName = "Source";
+ workspaceName = "default";
+ context = new ExecutionContext();
+ connection = new MockRepositoryConnection(sourceName, executedRequests);
+ stub(connectionFactory.createConnection(sourceName)).toReturn(connection);
+ graph = Graph.create(sourceName, connectionFactory, context);
+
+ validUuid = UUID.randomUUID();
+ validLocation = Location.create(validUuid);
+
+ // Stub out the repository, since we only need a few methods ...
+ repoTypeManager = new RepositoryNodeTypeManager(context);
+
+ PathFactory pathFactory = context.getValueFactories().getPathFactory();
+ stub(repository.getRepositoryTypeManager()).toReturn(repoTypeManager);
+ stub(repository.getRepositorySourceName()).toReturn(sourceName);
+
stub(repository.getPersistentRegistry()).toReturn(context.getNamespaceRegistry());
+ stub(repository.createWorkspaceGraph(anyString())).toAnswer(new
Answer<Graph>() {
+ public Graph answer( InvocationOnMock invocation ) throws Throwable {
+ 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;
+ }
+ });
+
+ executedRequests.clear();
+ }
+
+ protected Path createPath( String path ) {
+ return context.getValueFactories().getPathFactory().create(path);
+ }
+
+ protected Path createPath( Path parent,
+ String path ) {
+ return context.getValueFactories().getPathFactory().create(parent, path);
+ }
+
+ protected Name createName( String name ) {
+ return context.getValueFactories().getNameFactory().create(name);
+ }
+
+ protected Property createProperty( String name,
+ Object... values ) {
+ return context.getPropertyFactory().create(createName(name), values);
+ }
+
+ protected void assertNextRequestIsLock( Location at,
+ LockScope lockScope,
+ long lockTimeout ) {
+ Request request = executedRequests.poll();
+ assertThat(request, is(instanceOf(LockBranchRequest.class)));
+ LockBranchRequest lock = (LockBranchRequest)request;
+ assertThat(lock.at(), is(at));
+ assertThat(lock.lockScope(), is(lockScope));
+ assertThat(lock.lockTimeoutInMillis(), is(lockTimeout));
+ }
+
+ protected void assertNextRequestIsUnlock( Location at ) {
+ Request request = executedRequests.poll();
+ assertThat(request, is(instanceOf(UnlockBranchRequest.class)));
+ UnlockBranchRequest unlock = (UnlockBranchRequest)request;
+ assertThat(unlock.at(), is(at));
+ }
+
+ @Test
+ public void shouldCreateLockRequestWhenLockingNode() throws RepositoryException {
+ DnaLock lock = workspaceLockManager.createLock("testOwner",
UUID.randomUUID(), validUuid, false, false);
+ PropertyFactory propFactory = context.getPropertyFactory();
+ String lockOwner = "testOwner";
+ boolean isDeep = false;
+
+ Property lockOwnerProp = propFactory.create(JcrLexicon.LOCK_OWNER, lockOwner);
+ Property lockIsDeepProp = propFactory.create(JcrLexicon.LOCK_IS_DEEP, isDeep);
+
+ workspaceLockManager.lockNodeInRepository(validUuid, lockOwnerProp,
lockIsDeepProp, lock, isDeep);
+
+ assertNextRequestIsLock(validLocation, LockScope.SELF_ONLY, 0);
+ }
+
+ @Test
+ public void shouldCreateLockRequestWhenUnlockingNode() {
+ DnaLock lock = workspaceLockManager.createLock("testOwner",
UUID.randomUUID(), validUuid, false, false);
+ workspaceLockManager.unlockNodeInRepository(lock);
+
+ assertNextRequestIsUnlock(validLocation);
+ }
+
+}
Property changes on:
trunk/dna-jcr/src/test/java/org/jboss/dna/jcr/WorkspaceLockManagerTest.java
___________________________________________________________________
Name: svn:keywords
+ Id Revision
Name: svn:eol-style
+ LF
Modified:
trunk/extensions/dna-connector-infinispan/src/main/java/org/jboss/dna/connector/infinispan/InfinispanRepository.java
===================================================================
---
trunk/extensions/dna-connector-infinispan/src/main/java/org/jboss/dna/connector/infinispan/InfinispanRepository.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/extensions/dna-connector-infinispan/src/main/java/org/jboss/dna/connector/infinispan/InfinispanRepository.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -4,10 +4,12 @@
import org.infinispan.Cache;
import org.infinispan.manager.CacheManager;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.connector.LockFailedException;
import org.jboss.dna.graph.connector.map.AbstractMapWorkspace;
import org.jboss.dna.graph.connector.map.MapNode;
import org.jboss.dna.graph.connector.map.MapRepository;
import org.jboss.dna.graph.connector.map.MapWorkspace;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
/**
* The repository that uses an Infinispan instance.
@@ -80,6 +82,18 @@
assert nodeUuid != null;
return workspaceCache.get(nodeUuid);
}
+
+ @Override
+ public void lockNode( MapNode node,
+ LockScope lockScope,
+ long lockTimeoutInMillis ) throws LockFailedException {
+ // Locking is not supported by this connector
+ }
+
+ @Override
+ public void unlockNode( MapNode node ) {
+ // Locking is not supported by this connector
+ }
}
}
Modified:
trunk/extensions/dna-connector-jbosscache/src/main/java/org/jboss/dna/connector/jbosscache/JBossCacheRepository.java
===================================================================
---
trunk/extensions/dna-connector-jbosscache/src/main/java/org/jboss/dna/connector/jbosscache/JBossCacheRepository.java 2009-10-21
17:29:30 UTC (rev 1303)
+++
trunk/extensions/dna-connector-jbosscache/src/main/java/org/jboss/dna/connector/jbosscache/JBossCacheRepository.java 2009-10-21
22:12:10 UTC (rev 1304)
@@ -5,10 +5,12 @@
import org.jboss.cache.Fqn;
import org.jboss.cache.Node;
import org.jboss.dna.graph.ExecutionContext;
+import org.jboss.dna.graph.connector.LockFailedException;
import org.jboss.dna.graph.connector.map.AbstractMapWorkspace;
import org.jboss.dna.graph.connector.map.MapNode;
import org.jboss.dna.graph.connector.map.MapRepository;
import org.jboss.dna.graph.connector.map.MapWorkspace;
+import org.jboss.dna.graph.request.LockBranchRequest.LockScope;
/**
* A repository implementation that uses JBoss Cache.
@@ -81,6 +83,19 @@
assert nodeUuid != null;
return workspaceNode.get(nodeUuid);
}
+
+ @Override
+ public void lockNode( MapNode node,
+ LockScope lockScope,
+ long lockTimeoutInMillis ) throws LockFailedException {
+ // Locking is not supported by this connector
+ }
+
+ @Override
+ public void unlockNode( MapNode node ) {
+ // Locking is not supported by this connector
+ }
+
}
}