SLING-6388 Support tracking changes of JCR Mock MockSession (patch provided by Dirk Rudolph)

git-svn-id: https://svn.apache.org/repos/asf/sling/trunk@1773566 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
index 38870cd..4394366 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/AbstractItem.java
@@ -62,7 +62,7 @@
 
     @Override
     public boolean isModified() {
-        return false;
+        return itemData.isChanged();
     }
 
     @Override
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
index 538844f..1689d6b 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/ItemData.java
@@ -38,6 +38,7 @@
     private Value[] values;
     private boolean isMultiple;
     private boolean isNew;
+    private boolean isChanged;
     
     private ItemData(String path, boolean isNode, String uuid, NodeType nodeType) {
         this.path = path;
@@ -46,6 +47,7 @@
         this.isNode = isNode;
         this.nodeType = nodeType;
         this.isNew = true;
+        this.isChanged = false;
     }
     
     public String getPath() {
@@ -90,6 +92,7 @@
             throw new UnsupportedOperationException();
         }
         this.values = values;
+        this.isChanged = true;
     }
 
     public boolean isMultiple() {
@@ -104,6 +107,7 @@
             throw new UnsupportedOperationException();
         }
         this.isMultiple = isMultiple;
+        this.isChanged = true;
     }
     
     public Item getItem(Session session) {
@@ -123,6 +127,15 @@
         this.isNew = isNew;
     }
 
+    public boolean isChanged() {
+        return !isNew() && this.isChanged;
+    }
+
+    public void setIsChanged(boolean isChanged) {
+        this.isChanged = isChanged;
+    }
+
+
     @Override
     public int hashCode() {
         return path.hashCode();
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
index cfed57d..d1b9e56 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockNode.java
@@ -172,6 +172,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -181,6 +182,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(values);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -190,6 +192,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(values);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -199,6 +202,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -209,6 +213,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -218,6 +223,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -227,6 +233,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -236,6 +243,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -245,6 +253,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -254,6 +263,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -263,6 +273,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
@@ -272,6 +283,7 @@
         Property property = new MockProperty(itemData, getSession());
         property.setValue(value);
         getMockedSession().addItem(itemData);
+        this.itemData.setIsChanged(true);
         return property;
     }
 
diff --git a/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
index 0499a3c..f886a16 100644
--- a/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
+++ b/src/main/java/org/apache/sling/testing/mock/jcr/MockSession.java
@@ -56,16 +56,19 @@
     private final Map<String, ItemData> items;
     private final String userId;
     private boolean isLive;
+    private boolean hasKnownChanges;
 
-    public MockSession(MockRepository repository, Map<String,ItemData> items,
-            String userId, String workspaceName) {
+    public MockSession(MockRepository repository, Map<String, ItemData> items,
+            String userId, String workspaceName) throws RepositoryException {
         this.repository = repository;
         this.workspace = new MockWorkspace(repository, this, workspaceName);
         this.items = items;
         this.userId = userId;
         isLive = true;
+        hasKnownChanges = false;
+        this.save();
     }
-    
+
     private void checkLive() throws RepositoryException {
         if (!isLive) {
             throw new RepositoryException("Session is logged out / not live.");
@@ -192,6 +195,8 @@
         for (String pathToRemove : pathsToRemove) {
             this.items.remove(pathToRemove);
         }
+
+        hasKnownChanges = true;
     }
 
     RangeIterator listChildren(final String parentPath, final ItemFilter filter) throws RepositoryException {
@@ -216,6 +221,17 @@
     @Override
     public boolean hasPendingChanges() throws RepositoryException {
         checkLive();
+
+        if (hasKnownChanges) {
+            return true;
+        }
+
+        for (final ItemData item : this.items.values()) {
+            if (item.isNew() || item.isChanged()) {
+                return true;
+            }
+        }
+
         return false;
     }
 
@@ -270,7 +286,10 @@
         // reset new flags
         for (ItemData itemData : this.items.values()) {
             itemData.setIsNew(false);
+            itemData.setIsChanged(false);
         }
+
+        hasKnownChanges = false;
     }
 
     @Override
@@ -294,7 +313,7 @@
     public void logout() {
         isLive = false;
     }
-    
+
     // --- unsupported operations ---
     @Override
     public void addLockToken(final String lt) {
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
index 081c643..3cfd72b 100644
--- a/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockNodeTest.java
@@ -168,5 +168,19 @@
     public void testGetMixinNodeTypes() throws Exception {
         assertEquals(0, this.node1.getMixinNodeTypes().length);
     }
+
+    @Test
+    public void testIsModified() throws RepositoryException {
+        Node foo = this.session.getRootNode().addNode("foo");
+        // according to "if this Item has been saved but has subsequently been modified through
+        // the current session.
+        assertFalse(foo.isModified());
+        this.session.save();
+        assertFalse(foo.isModified());
+        foo.setProperty("bar", 1);
+        assertTrue(foo.isModified());
+        this.session.save();
+        assertFalse(foo.isModified());
+    }
     
 }
diff --git a/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
index 7bbe250..be50aff 100644
--- a/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
+++ b/src/test/java/org/apache/sling/testing/mock/jcr/MockSessionTest.java
@@ -34,23 +34,17 @@
 import javax.jcr.Session;
 
 import org.apache.jackrabbit.JcrConstants;
-import org.junit.Before;
 import org.junit.Test;
 
 import com.google.common.collect.ImmutableSet;
 
 public class MockSessionTest {
-
-    private Session session;
-
-    @Before
-    public void setUp() {
-        this.session = MockJcr.newSession();
-    }
-
+    
     @Test
     public void testEmptySession() throws RepositoryException {
-        Node rootNode = this.session.getRootNode();
+        Session session = MockJcr.newSession();
+        
+        Node rootNode = session.getRootNode();
         assertNotNull(rootNode);
         assertFalse(rootNode.getProperties().hasNext());
         assertFalse(rootNode.getNodes().hasNext());
@@ -74,8 +68,10 @@
 	
     @Test
     public void testNodePropertyCreateRead() throws RepositoryException {
-        Node rootNode = this.session.getNode("/");
-        assertEquals(rootNode, this.session.getRootNode());
+        Session session = MockJcr.newSession();
+        
+        Node rootNode = session.getNode("/");
+        assertEquals(rootNode, session.getRootNode());
 
         Node node1 = rootNode.addNode("node1");
         node1.setProperty("prop1a", "value1a");
@@ -85,41 +81,41 @@
         node2.setProperty("prop2", "value2");
 
         assertEquals(node1, rootNode.getNode("node1"));
-        assertEquals(node1, this.session.getNode("/node1"));
-        assertEquals(node1, this.session.getItem("/node1"));
-        assertEquals(node1, this.session.getNodeByIdentifier(node1.getIdentifier()));
-        assertTrue(this.session.nodeExists("/node1"));
-        assertTrue(this.session.itemExists("/node1"));
+        assertEquals(node1, session.getNode("/node1"));
+        assertEquals(node1, session.getItem("/node1"));
+        assertEquals(node1, session.getNodeByIdentifier(node1.getIdentifier()));
+        assertTrue(session.nodeExists("/node1"));
+        assertTrue(session.itemExists("/node1"));
         assertEquals(node2, rootNode.getNode("node2"));
-        assertEquals(node2, this.session.getNode("/node2"));
-        assertEquals(node2, this.session.getItem("/node2"));
-        assertEquals(node2, this.session.getNodeByIdentifier(node2.getIdentifier()));
-        assertTrue(this.session.nodeExists("/node2"));
-        assertTrue(this.session.itemExists("/node2"));
+        assertEquals(node2, session.getNode("/node2"));
+        assertEquals(node2, session.getItem("/node2"));
+        assertEquals(node2, session.getNodeByIdentifier(node2.getIdentifier()));
+        assertTrue(session.nodeExists("/node2"));
+        assertTrue(session.itemExists("/node2"));
 
         Property prop1a = node1.getProperty("prop1a");
         Property prop1b = node1.getProperty("prop1b");
         Property prop2 = node2.getProperty("prop2");
 
-        assertEquals(prop1a, this.session.getProperty("/node1/prop1a"));
-        assertEquals(prop1a, this.session.getItem("/node1/prop1a"));
-        assertTrue(this.session.propertyExists("/node1/prop1a"));
-        assertTrue(this.session.itemExists("/node1/prop1a"));
-        assertEquals(prop1b, this.session.getProperty("/node1/prop1b"));
-        assertEquals(prop1b, this.session.getItem("/node1/prop1b"));
-        assertTrue(this.session.propertyExists("/node1/prop1b"));
-        assertTrue(this.session.itemExists("/node1/prop1b"));
-        assertEquals(prop2, this.session.getProperty("/node2/prop2"));
-        assertEquals(prop2, this.session.getItem("/node2/prop2"));
-        assertTrue(this.session.propertyExists("/node2/prop2"));
-        assertTrue(this.session.itemExists("/node2/prop2"));
+        assertEquals(prop1a, session.getProperty("/node1/prop1a"));
+        assertEquals(prop1a, session.getItem("/node1/prop1a"));
+        assertTrue(session.propertyExists("/node1/prop1a"));
+        assertTrue(session.itemExists("/node1/prop1a"));
+        assertEquals(prop1b, session.getProperty("/node1/prop1b"));
+        assertEquals(prop1b, session.getItem("/node1/prop1b"));
+        assertTrue(session.propertyExists("/node1/prop1b"));
+        assertTrue(session.itemExists("/node1/prop1b"));
+        assertEquals(prop2, session.getProperty("/node2/prop2"));
+        assertEquals(prop2, session.getItem("/node2/prop2"));
+        assertTrue(session.propertyExists("/node2/prop2"));
+        assertTrue(session.itemExists("/node2/prop2"));
 
         assertEquals("value1a", prop1a.getString());
         assertEquals("value1b", prop1b.getString());
         assertEquals("value2", prop2.getString());
 
-        assertFalse(this.session.propertyExists("/node1"));
-        assertFalse(this.session.nodeExists("/node1/prop1a"));
+        assertFalse(session.propertyExists("/node1"));
+        assertFalse(session.nodeExists("/node1/prop1a"));
 
         assertEquals(JcrConstants.NT_UNSTRUCTURED, node1.getPrimaryNodeType().getName());
         assertTrue(node1.isNodeType(JcrConstants.NT_UNSTRUCTURED));
@@ -129,17 +125,21 @@
 
     @Test
     public void testNodeRemove() throws RepositoryException {
-        Node rootNode = this.session.getRootNode();
+        Session session = MockJcr.newSession();
+
+        Node rootNode = session.getRootNode();
         Node node1 = rootNode.addNode("node1");
-        assertTrue(this.session.itemExists("/node1"));
+        assertTrue(session.itemExists("/node1"));
         node1.remove();
-        assertFalse(this.session.itemExists("/node1"));
+        assertFalse(session.itemExists("/node1"));
         assertFalse(rootNode.getNodes().hasNext());
     }
 
     @Test
     public void testNodesWithSpecialNames() throws RepositoryException {
-        Node rootNode = this.session.getRootNode();
+        Session session = MockJcr.newSession();
+
+        Node rootNode = session.getRootNode();
 
         Node node1 = rootNode.addNode("node1.ext");
         Node node11 = node1.addNode("Node Name With Spaces");
@@ -147,9 +147,9 @@
         Node node12 = node1.addNode("node12_ext");
         node12.setProperty("prop12", "value12");
 
-        assertTrue(this.session.itemExists("/node1.ext"));
-        assertTrue(this.session.itemExists("/node1.ext/Node Name With Spaces"));
-        assertTrue(this.session.itemExists("/node1.ext/node12_ext"));
+        assertTrue(session.itemExists("/node1.ext"));
+        assertTrue(session.itemExists("/node1.ext/Node Name With Spaces"));
+        assertTrue(session.itemExists("/node1.ext/node12_ext"));
 
         assertEquals("value11", node11.getProperty("prop11").getString());
         assertEquals("value12", node12.getProperty("prop12").getString());
@@ -160,47 +160,59 @@
 
     @Test
     public void testItemsExists() throws RepositoryException {
-        assertFalse(this.session.nodeExists("/node1"));
-        assertFalse(this.session.itemExists("/node2"));
-        assertFalse(this.session.propertyExists("/node1/prop1"));
+        Session session = MockJcr.newSession();
+
+        assertFalse(session.nodeExists("/node1"));
+        assertFalse(session.itemExists("/node2"));
+        assertFalse(session.propertyExists("/node1/prop1"));
     }
 
     @Test(expected = PathNotFoundException.class)
     public void testNodeNotFoundException() throws RepositoryException {
-        this.session.getNode("/node1");
+        Session session = MockJcr.newSession();
+
+        session.getNode("/node1");
     }
 
     @Test(expected = PathNotFoundException.class)
     public void testPropertyNotFoundException() throws RepositoryException {
-        this.session.getProperty("/node1/prop1");
+        Session session = MockJcr.newSession();
+
+        session.getProperty("/node1/prop1");
     }
 
     @Test(expected = PathNotFoundException.class)
     public void testItemNotFoundException() throws RepositoryException {
-        this.session.getItem("/node2");
+        Session session = MockJcr.newSession();
+
+        session.getItem("/node2");
     }
 
     @Test(expected = ItemNotFoundException.class)
     public void testIdentifierFoundException() throws RepositoryException {
-        this.session.getNodeByIdentifier("unknown");
+        Session session = MockJcr.newSession();
+
+        session.getNodeByIdentifier("unknown");
     }
 
     @Test
     public void testNamespaces() throws RepositoryException {
+        Session session = MockJcr.newSession();
+
         // test initial namespaces
-        assertArrayEquals(new String[] { "jcr" }, this.session.getNamespacePrefixes());
-        assertEquals("http://www.jcp.org/jcr/1.0", this.session.getNamespaceURI("jcr"));
-        assertEquals("jcr", this.session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
+        assertArrayEquals(new String[] { "jcr" }, session.getNamespacePrefixes());
+        assertEquals("http://www.jcp.org/jcr/1.0", session.getNamespaceURI("jcr"));
+        assertEquals("jcr", session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
 
         // add dummy namespace
-        this.session.setNamespacePrefix("dummy", "http://mydummy");
+        session.setNamespacePrefix("dummy", "http://mydummy");
 
-        assertEquals(ImmutableSet.of("jcr", "dummy"), ImmutableSet.copyOf(this.session.getNamespacePrefixes()));
-        assertEquals("http://mydummy", this.session.getNamespaceURI("dummy"));
-        assertEquals("dummy", this.session.getNamespacePrefix("http://mydummy"));
+        assertEquals(ImmutableSet.of("jcr", "dummy"), ImmutableSet.copyOf(session.getNamespacePrefixes()));
+        assertEquals("http://mydummy", session.getNamespaceURI("dummy"));
+        assertEquals("dummy", session.getNamespacePrefix("http://mydummy"));
 
         // test via namespace registry
-        NamespaceRegistry namespaceRegistry = this.session.getWorkspace().getNamespaceRegistry();
+        NamespaceRegistry namespaceRegistry = session.getWorkspace().getNamespaceRegistry();
 
         assertEquals(ImmutableSet.of("jcr", "dummy"), ImmutableSet.copyOf(namespaceRegistry.getPrefixes()));
         assertEquals(ImmutableSet.of("http://www.jcp.org/jcr/1.0", "http://mydummy"),
@@ -211,14 +223,16 @@
         // remove dummy namespace
         namespaceRegistry.unregisterNamespace("dummy");
 
-        assertEquals(ImmutableSet.of("jcr"), ImmutableSet.copyOf(this.session.getNamespacePrefixes()));
-        assertEquals("http://www.jcp.org/jcr/1.0", this.session.getNamespaceURI("jcr"));
-        assertEquals("jcr", this.session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
+        assertEquals(ImmutableSet.of("jcr"), ImmutableSet.copyOf(session.getNamespacePrefixes()));
+        assertEquals("http://www.jcp.org/jcr/1.0", session.getNamespaceURI("jcr"));
+        assertEquals("jcr", session.getNamespacePrefix("http://www.jcp.org/jcr/1.0"));
     }
 
     @Test
     public void testUserId() {
-        assertEquals(MockJcr.DEFAULT_USER_ID, this.session.getUserID());
+        Session session = MockJcr.newSession();
+
+        assertEquals(MockJcr.DEFAULT_USER_ID, session.getUserID());
     }
 
     @Test
@@ -230,54 +244,87 @@
 
     @Test
     public void testSaveRefresh() throws RepositoryException {
+        Session session = MockJcr.newSession();
+
         // methods can be called without any effect
-        assertFalse(this.session.hasPendingChanges());
-        this.session.save();
-        this.session.refresh(true);
-        this.session.refresh(false);
+        assertFalse(session.hasPendingChanges());
+        session.save();
+        session.refresh(true);
+        session.refresh(false);
+    }
+
+    @Test
+    public void testHasPendingChanges() throws RepositoryException {
+        Session session = MockJcr.newSession();
+
+        Node foo = session.getRootNode().addNode("foo");
+        assertTrue(session.hasPendingChanges());
+        session.save();
+        assertFalse(session.hasPendingChanges());
+        foo.setProperty("bar1", "foobar");
+        assertTrue(session.hasPendingChanges());
+        session.save();
+        assertFalse(session.hasPendingChanges());
+        foo.getProperty("bar1").remove();
+        assertTrue(session.hasPendingChanges());
+        session.save();
+        assertFalse(session.hasPendingChanges());
+        foo.remove();
+        assertTrue(session.hasPendingChanges());
+        session.save();
+        assertFalse(session.hasPendingChanges());
     }
 
     @Test
     public void testGetRepository() {
-        assertNotNull(this.session.getRepository());
+        Session session = MockJcr.newSession();
+
+        assertNotNull(session.getRepository());
     }
 
     @Test
     public void testCheckPermission() throws RepositoryException {
-        this.session.checkPermission("/any/path", "anyActions");
+        Session session = MockJcr.newSession();
+
+        session.checkPermission("/any/path", "anyActions");
     }
 
     @Test
     public void testPathsAreNormalized() throws RepositoryException {
+        Session session = MockJcr.newSession();
         // 3.4.6 Passing Paths
         // When a JCR path is passed as an argument to a JCR method it may be normalized
         // or non-normalized and in standard or non-standard form.
 
-        this.session.getRootNode().addNode("foo");
-        assertTrue("Requesting node /foo/ should succeed", this.session.nodeExists("/foo/"));
-        assertTrue("Requesting item /foo/ should succeed", this.session.itemExists("/foo/"));
+        session.getRootNode().addNode("foo");
+        assertTrue("Requesting node /foo/ should succeed", session.nodeExists("/foo/"));
+        assertTrue("Requesting item /foo/ should succeed", session.itemExists("/foo/"));
 
-        this.session.getRootNode().addNode("bar/");
-        assertTrue("Creating /bar/ should succeed", this.session.nodeExists("/bar"));
+        session.getRootNode().addNode("bar/");
+        assertTrue("Creating /bar/ should succeed", session.nodeExists("/bar"));
 
-        this.session.removeItem("/foo/");
-        assertFalse("Removing /foo/ should succeed", this.session.nodeExists("/foo"));
+        session.removeItem("/foo/");
+        assertFalse("Removing /foo/ should succeed", session.nodeExists("/foo"));
     }
     
     @Test
     public void testNewState() throws RepositoryException {
-        Node node = this.session.getRootNode().addNode("foo");
+        Session session = MockJcr.newSession();
+
+        Node node = session.getRootNode().addNode("foo");
         Property property = node.setProperty("testProp", "value123");
         assertTrue(node.isNew());
         assertTrue(property.isNew());
         
-        this.session.save();
+        session.save();
         assertFalse(node.isNew());
         assertFalse(property.isNew());
     }
 
     @Test
     public void testLogout() throws Exception {
+        Session session = MockJcr.newSession();
+
         assertTrue(session.isLive());
         session.logout();
         assertFalse(session.isLive());