Index: b/docs/reference/src/main/docbook/en-US/content/jcr/configuration.xml
===================================================================
--- a/docs/reference/src/main/docbook/en-US/content/jcr/configuration.xml (revision )
+++ b/docs/reference/src/main/docbook/en-US/content/jcr/configuration.xml (working copy)
@@ -328,6 +328,133 @@ configuration.loadFrom(configSource);
+
+ Repository system content
+
+ Each JCR repository contains information about the system in the "/jcr:system" area of the repository content.
+ All of this system content applies to the whole repository (e.g., namespaces, node types, locks, versions, etc.) and
+ therefore every session for each workspace sees the exact same "/jcr:system" content.
+
+
+ ModeShape implements this behavior by storing all "/jcr:system" content in a separate workspace, and then
+ using federation to project that content into each workspace. This ensures
+ that all workspaces see the same content, without having to duplicate the "/jcr:system" content in each workspace
+ and ensure those copies stay in sync. Federation is better than duplication.
+
+
+ By default, ModeShape creates this separate system workspace in a transient, in-memory store. This works great for some
+ simplistic cases, but this doesn't work when using clustering,
+ versioning, or dynamically registering namespaces or
+ adding or changing node types.
+ This is because these features all rely upon changing or adding content in the "/jcr:system"
+ area. For example, version histories are stored under "/jcr:system/jcr:versionStorage", node types
+ under "/jcr:system/jcr:versionStorage", and namespaces under "/jcr:system/mode:namespaces".
+
+
+ In these situations, it is necessary to persist the system content in a repository source, and if clustering is enabled
+ this source needs to be accessible to all members of the cluster. Many times, the easiest approach is to simply define
+ an extra workspace in your repository source where the system content can be stored. It's also possible to define
+ a separate repository source with a separate workspace for each repository's system content. (Using a separate source is required
+ when the repository is using a single repository source that can only store limited kinds of nodes, like the
+ file system connector or Subversion connector
+ that can only store nt:file and nt:folder nodes.)
+
+
+ You should always configure each ModeShape repository with a source for its system workspace by using the
+ SYSTEM_WORKSPACE_NAME repository option with a value that defines the name of source and name of the workspace
+ in that source where the system content should be stored, in the format:
+
+ workspaceName@sourceName
+
+ This specifies the system content should be stored in the workspace named "workspaceName" in the
+ "sourceName" repository source.
+
+
+ The system content can be stored in any repository source capable of storing any content and, in the case
+ of clustering, is accessible across multiple processes. For most people, this will mean a relational database.
+ Here is an abbreviated example of an XML configuration that defines a source for the system storage (in a MySQL database)
+ and a repository that uses it:
+
+
+
+
+
+
+
+
+ ...
+
+ ...
+
+ ...
+
+
+
+
+
+
+
+ workspace1
+ workspace2
+ workspace3
+
+ ...
+
+ ...
+
+]]>
+
+ Of course, you can always use a separate workspace in your main source, too:
+
+
+
+
+
+
+
+
+ ...
+
+ ...
+
+ ...
+
+
+
+
+ workspace1
+ workspace2
+ workspace3
+ system
+
+ ...
+
+ ...
+
+]]>
+ Clustering
@@ -457,6 +584,12 @@ configuration.loadFrom(configSource);
Note that the this example uses a child XML element for the "configuration", along with
a CDATA section, so that the XML configuration can be nested within the ModeShape configuration.
+
+
+ Remember to specify the system workspace name
+ for each repository that is clustered.
+
+ JGroups configuration
Index: b/docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml
===================================================================
--- a/docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (revision )
+++ b/docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (working copy)
@@ -168,6 +168,12 @@ try {
options available when defining property definitions (e.g., searchable, queryable, etc.). Note that node type discovery is
largely unchanged.
+
+
+ Remember to specify the system workspace name for your repositories
+ if dynamically adding or modifying node types. Otherwise, ModeShape will not persist your node type changes.
+
+ Queries
@@ -225,6 +231,12 @@ try {
be aware that many of the locking-related methods on &Node; were deprecated in JCR 2.0 and moved to the new &LockManager; interface.
However, locking semantics remain unchanged.
+
+
+ Remember to specify the system workspace name for your repositories
+ if clustering or if the lock information is to be persisted beyond the lifetime of the ModeShape engine.
+
+ Versioning
@@ -241,6 +253,12 @@ try {
be aware that many of the version-related methods on &Node; were deprecated in JCR 2.0 and moved to the new &VersionManager; interface.
Also, any reliance upon ModeShape's recursive restore operation must be changed, per the JCR 2.0 specification.
+
+
+ Remember to specify the system workspace name for your repositories
+ if using versioning. Otherwise, ModeShape will not persist your versioning information.
+
+ Importing and Exporting
Index: b/docs/reference/src/main/docbook/en-US/custom.dtd
===================================================================
--- a/docs/reference/src/main/docbook/en-US/custom.dtd (revision )
+++ b/docs/reference/src/main/docbook/en-US/custom.dtd (working copy)
@@ -231,6 +231,7 @@
JcrEngine">
JcrConfiguration">
JcrRepository">
+JcrRepository.Option">
JcrRepositoryFactory">
JcrSession">
JndiRepositoryFactory">
Index: b/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPath.java
===================================================================
--- a/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPath.java (revision )
+++ b/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/BasicPath.java (working copy)
@@ -23,6 +23,10 @@
*/
package org.modeshape.graph.property.basic;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@@ -52,9 +56,9 @@ public class BasicPath extends AbstractPath {
public static final Path PARENT_PATH = new BasicPath(Collections.singletonList(Path.PARENT_SEGMENT), false);
- private final List segments;
- private final boolean absolute;
- private final boolean normalized;
+ private/*final*/List segments;
+ private/*final*/boolean absolute;
+ private/*final*/boolean normalized;
/**
* @param segments the segments
@@ -130,4 +134,30 @@ public class BasicPath extends AbstractPath {
return this.segments.size();
}
+ /**
+ * Custom deserialization is needed, since the 'segments' list may not be serializable (e.g., java.util.RandomAccessSubList).
+ *
+ * @param aStream the input stream to which this object should be serialized; never null
+ * @throws IOException if there is a problem reading from the stream
+ * @throws ClassNotFoundException if there is a problem loading any required classes
+ */
+ @SuppressWarnings( "unchecked" )
+ private void readObject( ObjectInputStream aStream ) throws IOException, ClassNotFoundException {
+ absolute = aStream.readBoolean();
+ normalized = aStream.readBoolean();
+ segments = (List)aStream.readObject();
+ }
+
+ /**
+ * Custom serialization is needed, since the 'segments' list may not be serializable (e.g., java.util.RandomAccessSubList).
+ *
+ * @param aStream the input stream to which this object should be serialized; never null
+ * @throws IOException if there is a problem writing to the stream
+ */
+ private void writeObject( ObjectOutputStream aStream ) throws IOException {
+ aStream.writeBoolean(absolute);
+ aStream.writeBoolean(normalized);
+ aStream.writeObject(Collections.unmodifiableList(new ArrayList(segments))); // make a copy!
+ }
+
}
Index: b/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/GraphNamespaceRegistry.java
===================================================================
--- a/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/GraphNamespaceRegistry.java (revision )
+++ b/modeshape-graph/src/main/java/org/modeshape/graph/property/basic/GraphNamespaceRegistry.java (working copy)
@@ -30,10 +30,10 @@ import java.util.List;
import java.util.Set;
import net.jcip.annotations.NotThreadSafe;
import org.modeshape.common.util.CheckArg;
-import org.modeshape.graph.ModeShapeLexicon;
import org.modeshape.graph.Graph;
import org.modeshape.graph.JcrLexicon;
import org.modeshape.graph.Location;
+import org.modeshape.graph.ModeShapeLexicon;
import org.modeshape.graph.Node;
import org.modeshape.graph.Subgraph;
import org.modeshape.graph.property.Name;
@@ -94,6 +94,13 @@ public class GraphNamespaceRegistry implements NamespaceRegistry {
}
/**
+ * @return parentOfNamespaceNodes
+ */
+ public Path getParentOfNamespaceNodes() {
+ return parentOfNamespaceNodes;
+ }
+
+ /**
* {@inheritDoc}
*/
public String getNamespaceForPrefix( String prefix ) {
@@ -196,6 +203,9 @@ public class GraphNamespaceRegistry implements NamespaceRegistry {
return cache.getNamespaces();
}
+ /**
+ * Refresh the namespaces from the persistent store, and update the embedded cache. This operation is done atomically.
+ */
public void refresh() {
SimpleNamespaceRegistry newCache = new SimpleNamespaceRegistry();
initializeCacheFromStore(newCache);
Index: b/modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java
===================================================================
--- a/modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java (revision )
+++ b/modeshape-integration-tests/src/test/java/org/modeshape/test/integration/ClusteringTest.java (working copy)
@@ -44,6 +44,7 @@ import javax.jcr.observation.EventListener;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
+import org.modeshape.common.FixFor;
import org.modeshape.common.collection.Problem;
import org.modeshape.common.util.FileUtil;
import org.modeshape.connector.store.jpa.JpaSource;
@@ -88,12 +89,15 @@ public class ClusteringTest {
.setProperty("largeValueSizeInBytes", "150")
.setProperty("autoGenerateSchema", "update")
.setProperty("retryLimit", "3")
- .setProperty("showSql", "false");
+ .setProperty("showSql", "false")
+ .setProperty("defaultWorkspaceName", "content")
+ .setProperty("predefinedWorkspaceNames", new String[] {"content", "system"});
configuration.repository("cars")
.setSource("car-source")
.registerNamespace("car", "http://www.modeshape.org/examples/cars/1.0")
.addNodeTypes(resourceUrl("cars.cnd"))
- .setOption(Option.ANONYMOUS_USER_ROLES, ModeShapeRoles.ADMIN);
+ .setOption(Option.ANONYMOUS_USER_ROLES, ModeShapeRoles.ADMIN)
+ .setOption(Option.SYSTEM_SOURCE_NAME, "system@car-source");
configuration.clustering().setProperty("clusterName", "MyCluster");// .setProperty("configuration", "");
// Create an engine and use it to populate the source ...
@@ -182,16 +186,20 @@ public class ClusteringTest {
Session session2 = sessionFrom(engine2);
Session session3 = sessionFrom(engine3);
- int eventTypes = Event.NODE_ADDED | Event.NODE_REMOVED; // |Event.PROPERTY_ADDED|Event.PROPERTY_CHANGED|Event.PROPERTY_REMOVED
- CustomListener listener1 = addListenerTo(session1, eventTypes, 1);
- CustomListener listener2 = addListenerTo(session2, eventTypes, 1);
- CustomListener listener3 = addListenerTo(session3, eventTypes, 1);
- CustomListener remoteListener1 = addRemoteListenerTo(session1, eventTypes, 0);
- CustomListener remoteListener2 = addRemoteListenerTo(session2, eventTypes, 1);
- CustomListener remoteListener3 = addRemoteListenerTo(session2, eventTypes, 1);
+ // Make a place under which we can make our changes, because ADD events will be filtered by parent path ...
+ session1.getRootNode().addNode("Base");
+ session1.save();
+
+ int eventTypes = Event.NODE_ADDED | Event.NODE_REMOVED;
+ CustomListener listener1 = addListenerTo(session1, null, eventTypes, 1);
+ CustomListener listener2 = addListenerTo(session2, "/Base", eventTypes, 1);
+ CustomListener listener3 = addListenerTo(session3, "/Base", eventTypes, 1);
+ CustomListener remoteListener1 = addRemoteListenerTo(session1, "/Base", eventTypes, 0);
+ CustomListener remoteListener2 = addRemoteListenerTo(session2, "/Base", eventTypes, 1);
+ CustomListener remoteListener3 = addRemoteListenerTo(session2, "/Base", eventTypes, 1);
// Make some changes ...
- session1.getRootNode().addNode("SomeNewNode");
+ session1.getNode("/Base").addNode("SomeNewNode");
session1.save();
// Wait for all the listeners ...
@@ -219,6 +227,74 @@ public class ClusteringTest {
remoteListener3.checkObservedEvents();
}
+ @FixFor( "MODE-809" )
+ @Test
+ public void shouldUpdateWorkspaceNamespaceRegistryAcrossCluster() throws Exception {
+ Session session1 = sessionFrom(engine1);
+ Session session2 = sessionFrom(engine2);
+ Session session3 = sessionFrom(engine3);
+
+ // Register a new prefix in one session ...
+ String nsUri = "http://example.com/foo/bar/baz";
+ String nsPrefix = "foo";
+ session1.getWorkspace().getNamespaceRegistry().registerNamespace(nsPrefix, nsUri);
+
+ // Make a place under which we can make our changes, because ADD events will be filtered by parent path ...
+ session1.getRootNode().addNode("Base");
+ session1.save();
+
+ // Register some listeners for the workspace content changes we'll make shortly ...
+ int eventTypes = Event.NODE_ADDED | Event.NODE_REMOVED;
+ CustomListener listener1 = addListenerTo(session1, "/Base", eventTypes, 1);
+ CustomListener listener2 = addListenerTo(session2, "/Base", eventTypes, 1);
+ CustomListener listener3 = addListenerTo(session3, "/Base", eventTypes, 1);
+ CustomListener remoteListener1 = addRemoteListenerTo(session1, "/Base", eventTypes, 0);
+ CustomListener remoteListener2 = addRemoteListenerTo(session2, "/Base", eventTypes, 1);
+ CustomListener remoteListener3 = addRemoteListenerTo(session2, "/Base", eventTypes, 1);
+
+ // Now create a node using this namespace ...
+ String propName = "foo:description";
+ String propValue = "This is the foobar description";
+ Node newNode = session1.getNode("/Base").addNode("SomeNewNode");
+ newNode.setProperty(propName, propValue);
+ session1.save();
+ final String path = newNode.getPath();
+
+ // Wait for all the listeners ...
+ listener1.await();
+ listener2.await();
+ listener3.await();
+ remoteListener1.await();
+ remoteListener2.await();
+ remoteListener3.await();
+
+ // Disconnect the listeners ...
+ listener1.disconnect();
+ listener2.disconnect();
+ listener3.disconnect();
+ remoteListener1.disconnect();
+ remoteListener2.disconnect();
+ remoteListener3.disconnect();
+
+ // Now check the events ...
+ listener1.checkObservedEvents();
+ listener2.checkObservedEvents();
+ listener3.checkObservedEvents();
+ remoteListener1.checkObservedEvents();
+ remoteListener2.checkObservedEvents();
+ remoteListener3.checkObservedEvents();
+
+ // Check the namespace registry in each session ...
+ assertThat(session1.getWorkspace().getNamespaceRegistry().getURI(nsPrefix), is(nsUri));
+ assertThat(session2.getWorkspace().getNamespaceRegistry().getURI(nsPrefix), is(nsUri));
+ assertThat(session3.getWorkspace().getNamespaceRegistry().getURI(nsPrefix), is(nsUri));
+
+ // Now, load the node using the various sessions and verify the correct namespace registry has been used ...
+ assertThat(session1.getNode(path).getProperty(propName).getString(), is(propValue));
+ assertThat(session2.getNode(path).getProperty(propName).getString(), is(propValue));
+ assertThat(session3.getNode(path).getProperty(propName).getString(), is(propValue));
+ }
+
// ----------------------------------------------------------------------------------------------------------------
// Utility Methods
// ----------------------------------------------------------------------------------------------------------------
@@ -234,6 +310,7 @@ public class ClusteringTest {
* Add a listener for only remote events.
*
* @param session the session
+ * @param absPath the absolute path at or below which the listener is interested in
* @param eventTypes the type of events
* @param expectedEventCount the number of expected events
* @return the listener
@@ -241,11 +318,12 @@ public class ClusteringTest {
* @throws RepositoryException
*/
protected CustomListener addRemoteListenerTo( Session session,
+ String absPath,
int eventTypes,
int expectedEventCount )
throws UnsupportedRepositoryOperationException, RepositoryException {
CustomListener listener = new CustomListener(session, expectedEventCount);
- session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, null, true, null, null, true);
+ session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, absPath, true, null, null, true);
return listener;
}
@@ -253,6 +331,8 @@ public class ClusteringTest {
* Add a listener for local and remote events.
*
* @param session the session
+ * @param absPath the absolute path at or below which the listener is interested in, or null if the listener is interested in
+ * all nodes
* @param eventTypes the type of events
* @param expectedEventCount the number of expected events
* @return the listener
@@ -260,11 +340,12 @@ public class ClusteringTest {
* @throws RepositoryException
*/
protected CustomListener addListenerTo( Session session,
+ String absPath,
int eventTypes,
int expectedEventCount )
throws UnsupportedRepositoryOperationException, RepositoryException {
CustomListener listener = new CustomListener(session, expectedEventCount);
- session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, null, true, null, null, false);
+ session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, absPath, true, null, null, false);
return listener;
}
Index: b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java
===================================================================
--- a/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision )
+++ b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy)
@@ -30,6 +30,7 @@ import java.security.AccessControlContext;
import java.security.AccessControlException;
import java.security.AccessController;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
@@ -411,7 +412,7 @@ public class JcrRepository implements Repository {
private final String systemWorkspaceName;
private final Projection systemSourceProjection;
private final FederatedRepositorySource federatedSource;
- private final NamespaceRegistry persistentRegistry;
+ private final GraphNamespaceRegistry persistentRegistry;
private final RepositoryObservationManager repositoryObservationManager;
private final SecurityContext anonymousUserContext;
private final QueryParsers queryParsers;
@@ -542,7 +543,7 @@ public class JcrRepository implements Repository {
Name uriProperty = ModeShapeLexicon.URI;
PathFactory pathFactory = executionContext.getValueFactories().getPathFactory();
Path systemPath = pathFactory.create(JcrLexicon.SYSTEM);
- Path namespacesPath = pathFactory.create(systemPath, ModeShapeLexicon.NAMESPACES);
+ final Path namespacesPath = pathFactory.create(systemPath, ModeShapeLexicon.NAMESPACES);
PropertyFactory propertyFactory = executionContext.getPropertyFactory();
Property namespaceType = propertyFactory.create(JcrLexicon.PRIMARY_TYPE, ModeShapeLexicon.NAMESPACE);
@@ -703,7 +704,32 @@ public class JcrRepository implements Repository {
repositoryLockManager = new RepositoryLockManager(this);
- this.jcrSystemObservers = Collections.singletonList(repositoryLockManager);
+ // Create a system observer to update the namespace registry cache ...
+ final GraphNamespaceRegistry persistentRegistry = this.persistentRegistry;
+ final JcrSystemObserver namespaceObserver = new JcrSystemObserver() {
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.jcr.JcrSystemObserver#getObservedPath()
+ */
+ public Path getObservedPath() {
+ return namespacesPath;
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.graph.observe.Observer#notify(org.modeshape.graph.observe.Changes)
+ */
+ public void notify( Changes changes ) {
+ // These changes apply to anything at or below the namespaces path ...
+ persistentRegistry.refresh();
+ }
+ };
+
+ // Define the set of "/jcr:system" observers ...
+ this.jcrSystemObservers = Collections.unmodifiableList(Arrays.asList(new JcrSystemObserver[] {repositoryLockManager,
+ namespaceObserver}));
// This observer picks up notification of changes to the system graph in a cluster. It's a NOP if there is no cluster.
this.repositoryObservationManager.register(new SystemChangeObserver());
@@ -1640,10 +1666,6 @@ public class JcrRepository implements Repository {
// These are changes made locally by this repository ...
return changes;
}
- if (systemSourceName.equals(changedSourceName)) {
- // These are changes made locally by this repository ...
- return changes;
- }
if (repositorySourceName.equals(changedSourceName)) {
// These may be events generated locally or from a remote engine in the cluster ...
if (this.processId.equals(changes.getProcessId())) {
@@ -1656,6 +1678,11 @@ public class JcrRepository implements Repository {
return new Changes(changes.getProcessId(), changes.getContextId(), changes.getUserName(), sourceName,
changes.getTimestamp(), changes.getChangeRequests(), changes.getData());
}
+ assert !changedSourceName.equals(repositorySourceName);
+ if (systemSourceName.equals(changedSourceName)) {
+ // These are changes made locally by this repository ...
+ return changes;
+ }
return null;
}
@@ -1731,7 +1758,7 @@ public class JcrRepository implements Repository {
if (changedPath == null) continue;
for (JcrSystemObserver jcrSystemObserver : getSystemObservers()) {
- if (changedPath.isAtOrAbove(jcrSystemObserver.getObservedRootPath())) {
+ if (changedPath.isAtOrBelow(jcrSystemObserver.getObservedPath())) {
systemChanges.put(jcrSystemObserver, change);
}
}
Index: b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSystemObserver.java
===================================================================
--- a/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSystemObserver.java (revision )
+++ b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrSystemObserver.java (working copy)
@@ -3,8 +3,16 @@ package org.modeshape.jcr;
import org.modeshape.graph.observe.Observer;
import org.modeshape.graph.property.Path;
-public interface JcrSystemObserver extends Observer {
+/**
+ * An interface for observers of the "/jcr:system" content.
+ */
+interface JcrSystemObserver extends Observer {
- Path getObservedRootPath();
+ /**
+ * Get the (absolute) path in the "/jcr:system" subgraph below which this observer is interested in changes.
+ *
+ * @return the observed path in the system content; may not be null
+ */
+ Path getObservedPath();
}
Index: b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java
===================================================================
--- a/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (revision )
+++ b/modeshape-jcr/src/main/java/org/modeshape/jcr/JcrWorkspace.java (working copy)
@@ -170,6 +170,10 @@ class JcrWorkspace implements Workspace {
LocalNamespaceRegistry localRegistry = new LocalNamespaceRegistry(globalRegistry);
this.context = context.with(localRegistry);
+ // Pre-cache all of the namespaces to be a snapshot of what's in the global registry at this time.
+ // This behavior is specified in Section 3.5.2 of the JCR 2.0 specification.
+ localRegistry.getNamespaces();
+
// Now create a graph for the session ...
this.graph = this.repository.createWorkspaceGraph(this.name, this.context);
Index: b/modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java
===================================================================
--- a/modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (revision )
+++ b/modeshape-jcr/src/main/java/org/modeshape/jcr/RepositoryLockManager.java (working copy)
@@ -118,7 +118,8 @@ class RepositoryLockManager implements JcrSystemObserver {
for (Location lockLocation : locksGraph.getRoot().getChildren()) {
Node lockNode = locksGraph.getNode(lockLocation);
- Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED).getFirstValue());
+ Boolean isSessionScoped = booleanFactory.create(lockNode.getProperty(ModeShapeLexicon.IS_SESSION_SCOPED)
+ .getFirstValue());
if (!isSessionScoped) continue;
String lockingSession = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.LOCKING_SESSION).getFirstValue());
@@ -127,7 +128,8 @@ class RepositoryLockManager implements JcrSystemObserver {
if (activeSessionIds.contains(lockingSession)) {
systemGraph.set(ModeShapeLexicon.EXPIRATION_DATE).on(lockLocation).to(newExpirationDate);
} else {
- DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE).getFirstValue());
+ DateTime expirationDate = dateFactory.create(lockNode.getProperty(ModeShapeLexicon.EXPIRATION_DATE)
+ .getFirstValue());
// Destroy expired locks (if it was still held by an active session, it would have been extended by now)
if (expirationDate.isBefore(now)) {
String workspaceName = stringFactory.create(lockNode.getProperty(ModeShapeLexicon.WORKSPACE).getFirstValue());
@@ -142,8 +144,13 @@ class RepositoryLockManager implements JcrSystemObserver {
}
}
+ /**
+ * {@inheritDoc}
+ *
+ * @see org.modeshape.jcr.JcrSystemObserver#getObservedPath()
+ */
@Override
- public Path getObservedRootPath() {
+ public Path getObservedPath() {
return locksPath;
}
@@ -153,6 +160,10 @@ class RepositoryLockManager implements JcrSystemObserver {
assert change.changedLocation().hasPath();
Path changedPath = change.changedLocation().getPath();
+ if (changedPath.equals(locksPath)) {
+ // nothing to do with the "/jcr:system/mode:locks" node ...
+ continue;
+ }
assert locksPath.isAncestorOf(changedPath);
Segment rawUuid = changedPath.getLastSegment();
@@ -165,35 +176,32 @@ class RepositoryLockManager implements JcrSystemObserver {
switch (change.getType()) {
case CREATE_NODE:
- CreateNodeRequest create = (CreateNodeRequest) change;
-
- Property lockOwnerProp= null;
+ CreateNodeRequest create = (CreateNodeRequest)change;
+
+ Property lockOwnerProp = null;
Property lockUuidProp = null;
Property isDeepProp = null;
Property isSessionScopedProp = null;
-
+
for (Property prop : create.properties()) {
if (JcrLexicon.LOCK_OWNER.equals(prop.getName())) {
lockOwnerProp = prop;
- }
- else if (JcrLexicon.LOCK_IS_DEEP.equals(prop.getName())) {
+ } else if (JcrLexicon.LOCK_IS_DEEP.equals(prop.getName())) {
isDeepProp = prop;
- }
- else if (ModeShapeLexicon.IS_HELD_BY_SESSION.equals(prop.getName())) {
+ } else if (ModeShapeLexicon.IS_HELD_BY_SESSION.equals(prop.getName())) {
isSessionScopedProp = prop;
- }
- else if (JcrLexicon.UUID.equals(prop.getName())) {
+ } else if (JcrLexicon.UUID.equals(prop.getName())) {
isSessionScopedProp = prop;
}
}
-
+
String lockOwner = firstString(lockOwnerProp);
UUID lockUuid = firstUuid(lockUuidProp);
boolean isDeep = firstBoolean(isDeepProp);
boolean isSessionScoped = firstBoolean(isSessionScopedProp);
workspaceManager.lockNodeInternally(lockOwner, lockUuid, lockedNodeUuid, isDeep, isSessionScoped);
-
+
break;
case DELETE_BRANCH:
boolean success = workspaceManager.unlockNodeInternally(lockedNodeUuid);
@@ -202,37 +210,37 @@ class RepositoryLockManager implements JcrSystemObserver {
break;
default:
- assert false :"Unexpected change request: " + change;
+ assert false : "Unexpected change request: " + change;
}
-
+
}
}
- private final String string(Segment rawString) {
+ private final String string( Segment rawString ) {
ExecutionContext context = repository.getExecutionContext();
return context.getValueFactories().getStringFactory().create(rawString);
}
- private final String firstString(Property property) {
+ private final String firstString( Property property ) {
if (property == null) return null;
Object firstValue = property.getFirstValue();
-
+
ExecutionContext context = repository.getExecutionContext();
return context.getValueFactories().getStringFactory().create(firstValue);
}
- private final UUID firstUuid(Property property) {
+ private final UUID firstUuid( Property property ) {
if (property == null) return null;
Object firstValue = property.getFirstValue();
-
+
ExecutionContext context = repository.getExecutionContext();
return context.getValueFactories().getUuidFactory().create(firstValue);
}
-
- private final boolean firstBoolean(Property property) {
+
+ private final boolean firstBoolean( Property property ) {
if (property == null) return false;
Object firstValue = property.getFirstValue();
-
+
ExecutionContext context = repository.getExecutionContext();
return context.getValueFactories().getBooleanFactory().create(firstValue);
}