Index: extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java =================================================================== --- extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java (revision 1973) +++ extensions/modeshape-connector-infinispan/src/main/java/org/modeshape/connector/infinispan/InfinispanRepository.java (working copy) @@ -95,6 +95,6 @@ public class InfinispanRepository extends Repository repository, + protected MapTransaction( ExecutionContext context, + Repository repository, UUID rootNodeUuid ) { - super(repository.getContext(), repository, rootNodeUuid); + super(context, repository, rootNodeUuid); } /** Index: modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java (revision 1973) +++ modeshape-graph/src/main/java/org/modeshape/graph/connector/inmemory/InMemoryRepository.java (working copy) @@ -53,6 +53,6 @@ public class InMemoryRepository extends Repositorynull) * @param repositoryObservable the repository observable used to register JCR listeners (never null) * @throws IllegalArgumentException if either parameter is null */ - public JcrObservationManager( JcrSession session, - Observable repositoryObservable ) { + JcrObservationManager( JcrSession session, + Observable repositoryObservable ) { CheckArg.isNotNull(session, "session"); CheckArg.isNotNull(repositoryObservable, "repositoryObservable"); @@ -129,6 +139,8 @@ final class JcrObservationManager implements ObservationManager { this.valueFactories = this.session.getExecutionContext().getValueFactories(); this.stringFactory = this.valueFactories.getStringFactory(); this.workspaceName = this.session.getWorkspace().getName(); + this.systemWorkspaceName = this.session.repository().getSystemWorkspaceName(); + this.systemSourceName = this.session.repository().getSystemSourceName(); } /** @@ -236,6 +248,14 @@ final class JcrObservationManager implements ObservationManager { return workspaceName; } + final String getSystemWorkspaceName() { + return systemWorkspaceName; + } + + final String getSystemSourceName() { + return systemSourceName; + } + /** * Remove all of the listeners. This is typically called when the {@link JcrSession#logout() session logs out}. */ @@ -901,6 +921,8 @@ final class JcrObservationManager implements ObservationManager { */ @Override public void notify( Changes changes ) { + // This method should only be called when the events are coming from one of the repository sources + // managing content for this JCR Repository instance. // check source first if (!acceptBasedOnEventSource(changes)) { return; @@ -912,9 +934,15 @@ final class JcrObservationManager implements ObservationManager { // loop through changes saving the parent locations of the changed locations for (ChangeRequest request : changes.getChangeRequests()) { - // ignore all events other than those on this workspace ... - if (!getWorkspaceName().equals(request.changedWorkspace())) { - continue; + String changedWorkspaceName = request.changedWorkspace(); + // If this event is not from this session's workspace ... + if (!getWorkspaceName().equals(changedWorkspaceName)) { + // And not in the system workspace in the system source ... + if (!getSystemWorkspaceName().equals(changedWorkspaceName) + && !changes.getSourceName().equals(getSystemSourceName())) { + // We ignore it ... + continue; + } } Path changedPath = request.changedLocation().getPath(); Path parentPath = changedPath.getParent(); @@ -946,9 +974,15 @@ final class JcrObservationManager implements ObservationManager { JcrEventBundle bundle = new JcrEventBundle(netChanges.getTimestamp(), netChanges.getUserName(), userData); for (NetChange change : netChanges.getNetChanges()) { - // ignore all events other than those on this workspace ... - if (!getWorkspaceName().equals(change.getRepositoryWorkspaceName())) { - continue; + String changedWorkspaceName = change.getRepositoryWorkspaceName(); + // If this event is not from this session's workspace ... + if (!getWorkspaceName().equals(changedWorkspaceName)) { + // And not in the system workspace in the system source ... + if (!getSystemWorkspaceName().equals(changedWorkspaceName) + && !netChanges.getSourceName().equals(getSystemSourceName())) { + // We ignore it ... + continue; + } } // ignore if lock/unlock Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (revision 1973) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrRepository.java (working copy) @@ -466,9 +466,6 @@ public class JcrRepository implements Repository { this.options = Collections.unmodifiableMap(localOptions); } - // Initialize the observer, which receives events from all repository sources - this.repositoryObservationManager = new RepositoryObservationManager(repositoryObservable); - // Set up the system source ... String systemSourceNameValue = this.options.get(Option.SYSTEM_SOURCE_NAME); String systemSourceName = null; @@ -506,6 +503,7 @@ public class JcrRepository implements Repository { LOGGER.warn(msg, systemSourceNameValue); } } + InMemoryRepositorySource transientSystemSource = null; if (systemSourceName == null) { // Create the in-memory repository source that we'll use for the "/jcr:system" branch in this repository. // All workspaces will be set up with a federation connector that projects this system repository into @@ -513,7 +511,7 @@ public class JcrRepository implements Repository { // (The federation connector refers to this configuration as an "offset mirror".) systemWorkspaceName = "jcr:system"; systemSourceName = "jcr:system source"; - InMemoryRepositorySource transientSystemSource = new InMemoryRepositorySource(); + transientSystemSource = new InMemoryRepositorySource(); transientSystemSource.setName(systemSourceName); transientSystemSource.setDefaultWorkspaceName(systemWorkspaceName); connectionFactoryWithSystem = new DelegatingConnectionFactory(connectionFactory, transientSystemSource); @@ -648,6 +646,35 @@ public class JcrRepository implements Repository { this.queryManager = new RepositoryQueryManager.Disabled(this.sourceName); } + // Initialize the observer, which receives events from all repository sources + this.repositoryObservationManager = new RepositoryObservationManager(repositoryObservable); + if (transientSystemSource != null) { + // The transient RepositorySource for the system content is not in the RepositoryLibrary, so we need to observe it ... + final Observer observer = this.repositoryObservationManager; + final ExecutionContext context = executionContext; + transientSystemSource.initialize(new RepositoryContext() { + @Override + public Observer getObserver() { + return observer; + } + + @Override + public ExecutionContext getExecutionContext() { + return context; + } + + @Override + public Subgraph getConfiguration( int depth ) { + return null; // not needed for the in-memory transient repository + } + + @Override + public RepositoryConnectionFactory getRepositoryConnectionFactory() { + return null; // not needed for the in-memory transient repository + } + }); + } + /* * Set up the anonymous role, if appropriate */ @@ -866,6 +893,10 @@ public class JcrRepository implements Repository { return systemSourceName; } + String getSystemWorkspaceName() { + return systemWorkspaceName; + } + /** * Get the name of the source that we want to observe. * @@ -1579,6 +1610,8 @@ public class JcrRepository implements Repository { private final ExecutorService observerService = Executors.newSingleThreadExecutor(); private final CopyOnWriteArrayList observers = new CopyOnWriteArrayList(); private final Observable repositoryObservable; + private final String sourceName; + private final String systemSourceName; /** * @param repositoryObservable the repository library observable this observer should register with @@ -1586,6 +1619,8 @@ public class JcrRepository implements Repository { protected RepositoryObservationManager( Observable repositoryObservable ) { this.repositoryObservable = repositoryObservable; this.repositoryObservable.register(this); + this.sourceName = getObservableSourceName(); + this.systemSourceName = getSystemSourceName(); } /** @@ -1594,30 +1629,32 @@ public class JcrRepository implements Repository { * @see org.modeshape.graph.observe.Observer#notify(org.modeshape.graph.observe.Changes) */ public void notify( final Changes changes ) { - // We only care about events that come from the federated source ... - if (!changes.getSourceName().equals(getObservableSourceName())) return; - - // We're still in the thread where the connector published its changes, - // so we need to create a runnable that will send these changes to all - // of the observers at this moment. Because 'observers' is - // a CopyOnWriteArrayList, we can't old onto the list (because the list's content - // might change). Instead, hold onto the Iterator over the listeners, - // and that will be a snapshot of the listeners at this moment - if (observers.isEmpty()) return; - final Iterator observerIterator = observers.iterator(); - - Runnable sender = new Runnable() { - public void run() { - while (observerIterator.hasNext()) { - Observer observer = observerIterator.next(); - assert observer != null; - observer.notify(changes); + // We only care about events that come from the repository source or the system source ... + String changedSourceName = changes.getSourceName(); + if (sourceName.equals(changedSourceName) || systemSourceName.equals(changedSourceName)) { + + // We're still in the thread where the connector published its changes, + // so we need to create a runnable that will send these changes to all + // of the observers at this moment. Because 'observers' is + // a CopyOnWriteArrayList, we can't old onto the list (because the list's content + // might change). Instead, hold onto the Iterator over the listeners, + // and that will be a snapshot of the listeners at this moment + if (observers.isEmpty()) return; + final Iterator observerIterator = observers.iterator(); + + Runnable sender = new Runnable() { + public void run() { + while (observerIterator.hasNext()) { + Observer observer = observerIterator.next(); + assert observer != null; + observer.notify(changes); + } } - } - }; + }; - // Now let the executor service run this in another thread ... - this.observerService.execute(sender); + // Now let the executor service run this in another thread ... + this.observerService.execute(sender); + } } /** Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrObservationManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrObservationManagerTest.java (revision 1973) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrObservationManagerTest.java (working copy) @@ -24,6 +24,7 @@ package org.modeshape.jcr; import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNull.notNullValue; import static org.hamcrest.core.IsNull.nullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.assertTrue; @@ -36,6 +37,7 @@ import java.util.UUID; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import javax.jcr.Credentials; +import javax.jcr.NamespaceRegistry; import javax.jcr.Node; import javax.jcr.Property; import javax.jcr.Repository; @@ -47,6 +49,8 @@ import javax.jcr.observation.Event; import javax.jcr.observation.EventIterator; import javax.jcr.observation.EventListener; import javax.jcr.observation.ObservationManager; +import javax.jcr.version.Version; +import javax.jcr.version.VersionHistory; import junit.framework.TestSuite; import org.apache.jackrabbit.test.api.observation.AddEventListenerTest; import org.apache.jackrabbit.test.api.observation.EventIteratorTest; @@ -67,6 +71,7 @@ import org.junit.Before; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; +import org.modeshape.common.FixFor; import org.modeshape.graph.connector.inmemory.InMemoryRepositorySource; import org.modeshape.graph.property.DateTime; import org.modeshape.jcr.JcrObservationManager.JcrEventBundle; @@ -670,7 +675,7 @@ public final class JcrObservationManagerTest extends TestSuite { save(); // register listener - TestListener listener = addListener(2, Event.PROPERTY_ADDED, null, false, null, null, false); + TestListener listener = addListener(2, 2, Event.PROPERTY_ADDED, testRootNode.getPath(), true, null, null, false); // lock node (no save needed) session.getWorkspace().getLockManager().lock(lockable.getPath(), false, true, 1L, "me"); @@ -1823,6 +1828,144 @@ public final class JcrObservationManagerTest extends TestSuite { checkResults(addListener2); } + @FixFor( "MODE-786" ) + @Test + public void shouldReceiveEventsForChangesToSessionNamespacesInSystemContent() throws Exception { + String uri = "http://acme.com/example/foobar/"; + String prefix = "foobar"; + assertNoSessionNamespace(uri, prefix); + + TestListener listener = addListener(session, 0, ALL_EVENTS, "/jcr:system", true, null, null, false); + session.setNamespacePrefix(prefix, uri); + + // Wait for the events on the session's listeners (that should NOT get the events) ... + listener.waitForEvents(); + removeListener(listener); + + // Verify the expected events were received ... + checkResults(listener); + } + + @FixFor( "MODE-786" ) + @Test + public void shouldReceiveEventsForChangesToRepositoryNamespacesInSystemContent() throws Exception { + String uri = "http://acme.com/example/foobar/"; + String prefix = "foobar"; + assertNoRepositoryNamespace(uri, prefix); + + Session session2 = login(REPOSITORY, "ws2", USER_ID, USER_ID.toCharArray()); + + TestListener listener = addListener(session, 3, ALL_EVENTS, "/jcr:system", true, null, null, false); + TestListener listener2 = addListener(session2, 3, ALL_EVENTS, "/jcr:system", true, null, null, false); + + session.getWorkspace().getNamespaceRegistry().registerNamespace(prefix, uri); + + // Wait for the events on the session's listeners (that should get the events) ... + listener.waitForEvents(); + listener2.waitForEvents(); + removeListener(listener); + removeListener(listener2); + + assertThat(session.getWorkspace().getNamespaceRegistry().getPrefix(uri), is(prefix)); + assertThat(session.getWorkspace().getNamespaceRegistry().getURI(prefix), is(uri)); + + // Verify the expected events were received ... + checkResults(listener); + checkResults(listener2); + } + + @FixFor( "MODE-786" ) + @Test + public void shouldReceiveEventsForChangesToLocksInSystemContent() throws Exception { + Node root = session.getRootNode(); + Node parentNode = root.addNode("lockedPropParent"); + parentNode.addMixin("mix:lockable"); + + Node targetNode = parentNode.addNode("lockedTarget"); + targetNode.setProperty("foo", "bar"); + session.save(); + + TestListener listener = addListener(session, 11, 2, ALL_EVENTS, "/jcr:system", true, null, null, false); + + lock(parentNode, true, true); // SHOULD GENERATE AN EVENT TO CREATE A LOCK + + // Wait for the events on the session's listeners (that should get the events) ... + listener.waitForEvents(); + removeListener(listener); + + // Verify the expected events were received ... + checkResults(listener); + } + + @FixFor( "MODE-786" ) + @Test + public void shouldReceiveEventsForChangesToVersionsInSystemContent() throws Exception { + TestListener listener = addListener(session, 15, ALL_EVENTS, "/jcr:system", true, null, null, false); + + Node node = session.getRootNode().addNode("/test", "nt:unstructured"); + node.addMixin("mix:versionable"); + session.save(); // SHOULD GENERATE AN EVENT TO CREATE VERSION HISTORY FOR THE NODE + + // Wait for the events on the session's listeners (that should get the events) ... + listener.waitForEvents(); + removeListener(listener); + + Node history = node.getProperty("jcr:versionHistory").getNode(); + assertThat(history, is(notNullValue())); + + assertThat(node.hasProperty("jcr:baseVersion"), is(true)); + Node version = node.getProperty("jcr:baseVersion").getNode(); + assertThat(version, is(notNullValue())); + + assertThat(version.getParent(), is(history)); + + assertThat(node.hasProperty("jcr:uuid"), is(true)); + assertThat(node.getProperty("jcr:uuid").getString(), is(history.getProperty("jcr:versionableUuid").getString())); + + assertThat(versionHistory(node).getIdentifier(), is(history.getIdentifier())); + assertThat(versionHistory(node).getPath(), is(history.getPath())); + + assertThat(baseVersion(node).getIdentifier(), is(version.getIdentifier())); + assertThat(baseVersion(node).getPath(), is(version.getPath())); + + // Verify the expected events were received ... + checkResults(listener); + } + + protected void assertNoRepositoryNamespace( String uri, + String prefix ) throws RepositoryException { + NamespaceRegistry registry = session.getWorkspace().getNamespaceRegistry(); + for (String existingPrefix : registry.getPrefixes()) { + assertThat(existingPrefix.equals(prefix), is(false)); + } + for (String existingUri : registry.getURIs()) { + assertThat(existingUri.equals(uri), is(false)); + } + } + + protected void assertNoSessionNamespace( String uri, + String prefix ) throws RepositoryException { + for (String existingPrefix : session.getNamespacePrefixes()) { + assertThat(existingPrefix.equals(prefix), is(false)); + String existingUri = session.getNamespaceURI(existingPrefix); + assertThat(existingUri.equals(uri), is(false)); + } + } + + protected VersionHistory versionHistory( Node node ) throws RepositoryException { + return session.getWorkspace().getVersionManager().getVersionHistory(node.getPath()); + } + + protected Version baseVersion( Node node ) throws RepositoryException { + return session.getWorkspace().getVersionManager().getBaseVersion(node.getPath()); + } + + protected void lock( Node node, + boolean isDeep, + boolean isSessionScoped ) throws RepositoryException { + session.getWorkspace().getLockManager().lock(node.getPath(), isDeep, isSessionScoped, 1L, "owner"); + } + // =========================================================================================================================== // Inner Class // =========================================================================================================================== Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (revision 1973) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrTckTest.java (working copy) @@ -145,6 +145,7 @@ import org.apache.jackrabbit.test.api.observation.GetDateTest; import org.apache.jackrabbit.test.api.observation.GetIdentifierTest; import org.apache.jackrabbit.test.api.observation.GetInfoTest; import org.apache.jackrabbit.test.api.observation.GetRegisteredEventListenersTest; +import org.apache.jackrabbit.test.api.observation.GetUserDataTest; import org.apache.jackrabbit.test.api.observation.LockingTest; import org.apache.jackrabbit.test.api.observation.NodeAddedTest; import org.apache.jackrabbit.test.api.observation.NodeMovedTest; @@ -530,7 +531,7 @@ public class JcrTckTest { addTestSuite(GetDateTest.class); addTestSuite(GetIdentifierTest.class); addTestSuite(GetInfoTest.class); - // addTestSuite(GetUserDataTest.class); // see MODE-786 + addTestSuite(GetUserDataTest.class); } }