/*
* ModeShape (http://www.modeshape.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.modeshape.jcr;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import javax.jcr.Node;
import javax.jcr.Session;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.modeshape.common.util.FileUtil;
import org.modeshape.jcr.bus.ChangeBus;
import org.modeshape.jcr.cache.change.Change;
import org.modeshape.jcr.cache.change.ChangeSet;
import org.modeshape.jcr.cache.change.ChangeSetListener;
import org.modeshape.jcr.cache.change.NodeAdded;
import org.modeshape.jcr.cache.change.NodeRemoved;
import org.modeshape.jcr.cache.change.PropertyAdded;
import org.modeshape.jcr.cache.change.RecordingChanges;
public class ConnectorChangesTest extends SingleUseAbstractTest {
private ChangeBus changeBus;
private TestListener listener;
@Before
@Override
public void beforeEach() throws Exception {
FileUtil.delete("target/files");
// Now start the repository ...
startRepositoryWithConfigurationFrom("config/repo-config-federation-changes.json");
printMessage("Started repository...");
changeBus = repository().changeBus();
}
@After
@Override
public void afterEach() throws Exception {
stopRepository();
printMessage("Stopped repository.");
FileUtil.delete("target/files");
}
@Test
public void testChangesEmittedWhenNodeCreatedAndRemoved() throws Exception {
final Session session = session();
final Node root = session.getRootNode();
printMessage("Root node is: ");
print(root, false);
Node projection1 = session.getNode("/projection1");
Node projection1Generate = session.getNode("/projection1/generate");
assertThat(projection1, is(notNullValue()));
assertThat(projection1Generate, is(notNullValue()));
// Let the existing startup-related events go through before we start listening ...
Thread.sleep(1000);
// expect 2 change sets (1 for the node we're adding, and one for the auto-generated events) ...
listener = new TestListener(2);
changeBus.register(listener);
// Now add a node under '/testRoot/projection1/generate' to cause some events to be created under
// '/testRoot/projection1/generated-out' ...
Node node = projection1Generate.addNode("testNode1", "nt:unstructured");
session.save();
// Wait until the listener gets some events ...
listener.await();
assertThat("Didn't receive changes after node creation!", listener.receivedChangeSet.size(), is(2));
// print = true;
printMessage("Received change sets: \n" + listener.receivedChangeSet);
// The first change set should be from the auto-created (since with the MockConnectorWithChanges will fire
// off the events *before* the 'session.save()' call above completes...
RecordingChanges changes = listener.receivedChangeSet.get(0);
Iterator<Change> iter = changes.iterator();
assertNodeAdded(iter.next(), "/projection1/generated-out/testNode1");
assertNodeAdded(iter.next(), "/projection1/generated-out/testNode1/child0");
assertNodeAdded(iter.next(), "/projection1/generated-out/testNode1/child1");
assertNodeAdded(iter.next(), "/projection1/generated-out/testNode1/child2");
assertThat(iter.hasNext(), is(false));
// The remaining change set is due to our manually-created node saved above ...
changes = listener.receivedChangeSet.get(1);
iter = changes.iterator();
assertNodeAdded(iter.next(), "/projection1/generate/testNode1");
assertPropertyAdded(iter.next(), "/projection1/generate/testNode1", "jcr:primaryType");
assertThat(iter.hasNext(), is(false));
changeBus.unregister(listener);
// expect 2 change sets (1 for the node we're adding, and one for the auto-generated events) ...
listener = new TestListener(2);
changeBus.register(listener);
// Remove the recently-added node ...
session.refresh(false);
node = session.getNode("/projection1/generate/testNode1");
node.remove();
session.save();
// Wait until the listener gets some events ...
listener.await();
assertThat("Didn't receive changes after node removal!", listener.receivedChangeSet.size(), is(2));
// print = true;
printMessage("Received change sets: \n" + listener.receivedChangeSet);
// The first change set should be from the automatic removal (since with the MockConnectorWithChanges will fire
// off the events *before* the 'session.save()' call above completes...
changes = listener.receivedChangeSet.get(0);
iter = changes.iterator();
assertNodeRemoved(iter.next(), "/projection1/generated-out/testNode1");
assertThat(iter.hasNext(), is(false));
// The remaining change set is due to our manually-created node saved above ...
changes = listener.receivedChangeSet.get(1);
iter = changes.iterator();
assertNodeRemoved(iter.next(), "/projection1/generate/testNode1");
assertThat(iter.hasNext(), is(false));
changeBus.unregister(listener);
}
protected void assertNodeAdded( Change change,
String path ) {
assertThat(change, is(instanceOf(NodeAdded.class)));
NodeAdded added = (NodeAdded)change;
assertThat(added.getPath(), is(path(path)));
}
protected void assertNodeRemoved( Change change,
String path ) {
assertThat(change, is(instanceOf(NodeRemoved.class)));
NodeRemoved removed = (NodeRemoved)change;
assertThat(removed.getPath(), is(path(path)));
}
protected void assertPropertyAdded( Change change,
String path,
String propertyName ) {
assertThat(change, is(instanceOf(PropertyAdded.class)));
PropertyAdded added = (PropertyAdded)change;
assertThat(added.getPath(), is(path(path)));
assertThat(added.getProperty().getName(), is(name(propertyName)));
}
protected static class TestListener implements ChangeSetListener {
protected final List<RecordingChanges> receivedChangeSet;
private CountDownLatch latch;
public TestListener() {
this(0);
}
protected TestListener( int expectedNumberOfChangeSet ) {
latch = new CountDownLatch(expectedNumberOfChangeSet);
receivedChangeSet = new ArrayList<RecordingChanges>();
}
public void expectChangeSet( int expectedNumberOfChangeSet ) {
latch = new CountDownLatch(expectedNumberOfChangeSet);
receivedChangeSet.clear();
}
@Override
public void notify( ChangeSet changeSet ) {
if (!(changeSet instanceof RecordingChanges)) {
throw new IllegalArgumentException("Invalid type of change set received");
}
receivedChangeSet.add((RecordingChanges)changeSet);
latch.countDown();
}
public void await() throws InterruptedException {
latch.await(5, TimeUnit.SECONDS);
}
public List<RecordingChanges> getObservedChangeSet() {
return receivedChangeSet;
}
}
}