Index: jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BenchmarkSuite.java
===================================================================
--- jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BenchmarkSuite.java	(revision 757735)
+++ jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BenchmarkSuite.java	(working copy)
@@ -26,5 +26,6 @@
         addTestSuite(BigCollectionTest.class);
         addTestSuite(RefreshTest.class);
         addTestSuite(LoginTest.class);
+        addTestSuite(BigTreeTest.class);
     }
 }
Index: jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BigTreeTest.java
===================================================================
--- jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BigTreeTest.java	(revision 0)
+++ jackrabbit-jcr-benchmark/src/main/java/org/apache/jackrabbit/benchmark/BigTreeTest.java	(revision 0)
@@ -0,0 +1,183 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.apache.jackrabbit.benchmark;
+
+import java.util.Random;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+
+public class BigTreeTest extends AbstractBenchmarkTest {
+    private static final Logger log = LoggerFactory.getLogger(BigTreeTest.class);
+
+    protected static final int CHILDCOUNT = 2;
+    protected static final int DEPTH = 3;
+    protected static final String NODE_NAME = "1";
+    protected static final String DEEP_NODE_NAME;
+
+    static {
+        String sep = "";
+        StringBuffer sb = new StringBuffer();
+        for (int i = 0; i < DEPTH; i++) {
+            sb.append(sep);
+            sb.append("1");
+            sep = "/";
+        }
+        DEEP_NODE_NAME = sb.toString();
+    }
+
+    private Node bigTree;
+
+    protected String getCollectionName() {
+        return "bigTree";
+    }
+
+    protected void setUp() throws Exception {
+        super.setUp();
+        bigTree = testRootNode.getNode(getCollectionName());
+    }
+
+    protected void createContent(Node folder) throws RepositoryException {
+        addChildNodes(0, folder);
+        folder.getSession().save();
+    }
+
+    private void addChildNodes(int depth, Node parent) throws RepositoryException {
+        if (depth < DEPTH) {
+            for (int i = 0; i < CHILDCOUNT; i++) {
+                Node c = parent.addNode("" + i, "nt:folder");
+                addChildNodes(depth + 1, c);
+            }
+        }
+    }
+
+    private void performTest(String name, boolean recursive, NodeNameProvier nodeNames) throws RepositoryException {
+        Timer getNodeTime = new Timer();
+        Timer refreshTime = new Timer();
+        int c = 0;
+
+        while (getNodeTime.getElapsedTime() + refreshTime.getElapsedTime() < MINTIME || c < MINCOUNT) {
+            String nodeName = nodeNames.getNodeName(c);
+            getNodeTime.start();
+            Node dir = bigTree.getNode(nodeName);
+            getNodeTime.stop();
+
+            refreshTime.start();
+            bigTree.refresh(recursive);
+            refreshTime.stop();
+            c++;
+        }
+
+        log.info(name + ": " + (double)getNodeTime.getElapsedTime()/c + "ms per getNode() (" + c + " iterations)");
+        log.info(name + ": " + (double)refreshTime.getElapsedTime()/c + "ms per refresh() (" + c + " iterations)");
+    }
+
+    public void testRefreshRecursive() throws RepositoryException {
+        performTest("testRefreshRecursive", true, new NodeNameProvier() {
+            public String getNodeName(int i) {
+                return NODE_NAME;
+            }
+        });
+    }
+
+    public void testRefreshNonRecursive() throws RepositoryException {
+        performTest("testRefreshNonRecursive", false, new NodeNameProvier() {
+            public String getNodeName(int i) {
+                return NODE_NAME;
+            }
+        });
+    }
+
+    public void testRefreshRecursiveDeep() throws RepositoryException {
+        performTest("testRefreshRecursiveDeep", true, new NodeNameProvier() {
+            public String getNodeName(int i) {
+                return DEEP_NODE_NAME;
+            }
+        });
+    }
+
+    public void testRefreshNonRecursiveDeep() throws RepositoryException {
+        performTest("testRefreshNonRecursiveDeep", false, new NodeNameProvier() {
+            public String getNodeName(int i) {
+                return DEEP_NODE_NAME;
+            }
+        });
+    }
+
+    public void testRefreshRecursiveRandom() throws RepositoryException {
+        performTest("testRefreshRecursiveRandom", true, new RandomNodeNameProvider());
+    }
+
+    public void testRefreshNonRecursiveRandom() throws RepositoryException {
+        performTest("testRefreshNonRecursiveRandom", false, new RandomNodeNameProvider());
+    }
+
+    // -----------------------------------------------------< NodeNameProvier >---
+
+    private static interface NodeNameProvier {
+        public String getNodeName(int i);
+    }
+
+    private class RandomNodeNameProvider implements NodeNameProvier {
+        private final Random rnd = new Random();
+
+        public String getNodeName(int i) {
+            StringBuffer sb = new StringBuffer();
+            int depth = rnd.nextInt(DEPTH);
+            String sep = "";
+            for (int k = 0; k <= depth; k++) {
+                sb.append(sep).append(rnd.nextInt(CHILDCOUNT));
+                sep = "/";
+            }
+            return sb.toString();
+        }
+    }
+
+    // -----------------------------------------------------< Timer >---
+
+    private static class Timer {
+        private long startTime;
+        private long elapsedTime;
+        private boolean started;
+
+        public void start() {
+            if (started) {
+                throw new IllegalStateException("Timer already started");
+            }
+            started = true;
+            startTime = System.currentTimeMillis();
+        }
+
+        public long stop() {
+            if (!started) {
+                throw new IllegalStateException("Timer not started");
+            }
+            elapsedTime += System.currentTimeMillis() - startTime;
+            started = false;
+            return elapsedTime;
+        }
+
+        public long getElapsedTime() {
+            return elapsedTime;
+        }
+    }
+
+}

Property changes on: jackrabbit-jcr-benchmark\src\main\java\org\apache\jackrabbit\benchmark\BigTreeTest.java
___________________________________________________________________
Added: svn:eol-style
   + native

Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntries.java	(working copy)
@@ -16,15 +16,16 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.Path;
+import java.util.Iterator;
+import java.util.List;
+import java.util.NoSuchElementException;
 
 import javax.jcr.ItemNotFoundException;
 import javax.jcr.RepositoryException;
-import java.util.List;
-import java.util.NoSuchElementException;
-import java.util.Iterator;
 
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
+
 /**
  * <code>ChildNodeEntries</code> represents a collection of <code>NodeEntry</code>s that
  * also maintains the index values of same-name siblings on insertion and removal.
@@ -39,12 +40,6 @@
     boolean isComplete();
 
     /**
-     * Mark <code>ChildNodeEntries</code> in order to force reloading the
-     * entries.
-     */
-    void invalidate();
-
-    /**
      * Reloads this <code>ChildNodeEntries</code> object.
      *
      * @throws ItemNotFoundException
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/ChildNodeEntriesImpl.java	(working copy)
@@ -16,28 +16,29 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.apache.jackrabbit.spi.Name;
-import org.apache.jackrabbit.spi.Path;
-import org.apache.jackrabbit.spi.ChildInfo;
-import org.apache.jackrabbit.spi.NodeId;
-import org.apache.jackrabbit.jcr2spi.state.Status;
-import org.apache.commons.collections.list.AbstractLinkedList;
-
-import javax.jcr.RepositoryException;
-import javax.jcr.ItemNotFoundException;
-import java.util.Iterator;
-import java.util.List;
-import java.util.NoSuchElementException;
+import java.lang.ref.Reference;
+import java.lang.ref.SoftReference;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.ConcurrentModificationException;
-import java.util.Map;
 import java.util.HashMap;
-import java.util.Collections;
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
 
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.collections.list.AbstractLinkedList;
+import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.apache.jackrabbit.spi.ChildInfo;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.NodeId;
+import org.apache.jackrabbit.spi.Path;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 /**
  * <code>ChildNodeEntriesImpl</code> implements a memory sensitive implementation
  * of the <code>ChildNodeEntries</code> interface.
@@ -49,7 +50,6 @@
     private static final int STATUS_OK = 0;
     private static final int STATUS_INVALIDATED = 1;
 
-    private int status = STATUS_OK;
     private boolean complete = false;
 
     /**
@@ -122,19 +122,12 @@
      * @see ChildNodeEntries#isComplete()
      */
     public boolean isComplete() {
-        return (status == STATUS_OK && complete) ||
+        return (parent.getStatus() != Status.INVALIDATED && complete) ||
                 parent.getStatus() == Status.NEW ||
                 Status.isTerminal(parent.getStatus());
     }
 
     /**
-     * @see ChildNodeEntries#invalidate()
-     */
-    public void invalidate() {
-        this.status = STATUS_INVALIDATED;
-    }
-
-    /**
      * @see ChildNodeEntries#reload()
      */
     public synchronized void reload() throws ItemNotFoundException, RepositoryException {
@@ -189,7 +182,6 @@
             prevLN = ln;
         }
         // finally reset the status
-        status = STATUS_OK;
         complete = true;
     }
 
@@ -699,7 +691,7 @@
         private class LinkNodeIterator implements Iterator {
 
             private LinkedEntries.LinkNode next = ((LinkedEntries.LinkNode) header).getNextLinkNode();
-            private int expectedModCount = modCount;
+            private final int expectedModCount = modCount;
 
             public boolean hasNext() {
                 checkModCount();
@@ -736,8 +728,8 @@
      */
     private static class NameMap {
 
-        private Map snsMap = new HashMap();
-        private Map nameMap = new HashMap();
+        private final Map snsMap = new HashMap();
+        private final Map nameMap = new HashMap();
 
         /**
          * Return true if more than one NodeEnty with the given name exists.
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/EntryFactory.java	(working copy)
@@ -16,15 +16,16 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
+import org.apache.jackrabbit.jcr2spi.hierarchy.HierarchyEntry.InvalidationStrategy;
 import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.util.LogUtil;
 import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PathFactory;
-import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.commons.conversion.NamePathResolver;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 /**
  * <code>EntryFactory</code>...
@@ -58,6 +59,11 @@
     private NamePathResolver resolver;
 
     /**
+     * Strategy used for item state invalidation (refresh)
+     */
+    private final InvalidationStrategy invalidationStrategy;
+
+    /**
      * Create a new instance of the <code>EntryFactory</code>.
      *
      * @param isf
@@ -71,6 +77,10 @@
         this.pathFactory = pathFactory;
         this.isf = isf;
         this.listener = listener;
+
+        // todo: make this configurable if necessary
+        // this.invalidationStrategy = new NodeEntryImpl.EagerInvalidation();
+        this.invalidationStrategy = new NodeEntryImpl.LazyInvalidation();
         this.rootEntry = NodeEntryImpl.createRootEntry(this);
     }
 
@@ -115,6 +125,13 @@
         listener.uniqueIdChanged(entry, previousUniqueID);
     }
 
+    /**
+     * @return  the strategy used for item state invalidation (refresh)
+     */
+    public InvalidationStrategy getInvalidationStrategy() {
+        return invalidationStrategy;
+    }
+
     //--------------------------------------------------------------------------
     /**
      * @param resolver
@@ -134,7 +151,7 @@
             return LogUtil.safeGetJCRPath(path, resolver);
         }
     }
-    
+
     //--------------------------------------------------------------------------
     public interface NodeEntryListener {
 
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntry.java	(working copy)
@@ -16,16 +16,16 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.Status;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
-import org.apache.jackrabbit.jcr2spi.state.ItemState;
-import org.apache.jackrabbit.jcr2spi.state.Status;
-import org.apache.jackrabbit.jcr2spi.operation.Operation;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.ItemNotFoundException;
-
 /**
  * <code>HierarchyEntry</code>...
  */
@@ -112,16 +112,21 @@
     /**
      * Invalidates the underlying <code>ItemState</code> if available and if it
      * is not transiently modified. If the <code>recursive</code> flag is true,
-     * the hierarchy is traverses and {@link #invalidate(boolean)} is called on
-     * all child entries.<br>
+     * also invalidates the child entries recursively.<br>
      * Note, that in contrast to {@link HierarchyEntry#reload(boolean)}
      * this method only sets the status of this item state to {@link
-     * Status#INVALIDATED} and does not acutally update it with the persistent
+     * Status#INVALIDATED} and does not actually update it with the persistent
      * state in the repository.
      */
     public void invalidate(boolean recursive);
 
     /**
+     * Calculates the status of the underlying <code>ItemState</code>: any pending
+     * changes to the underlying <code>ItemState</code> are applied.
+     */
+    public void calculateStatus();
+
+    /**
      * Traverses the hierarchy and reverts all transient modifications such as
      * adding, modifying or removing item states. 'Existing' item states
      * are reverted to their initial state and their status is reset to {@link Status#EXISTING}.
@@ -167,4 +172,30 @@
      * @param transientOperation
      */
     public void complete(Operation transientOperation) throws RepositoryException;
+
+    /**
+     * Strategy for invalidating item states
+     */
+    public interface InvalidationStrategy {
+
+        /**
+         * Invalidate underlying {@link ItemState} of this <code>entry</code>.
+         * Implementors may choose to delay the actual call to {@link ItemState#invalidate()}
+         * for this <code>entry</code> and for any of its child entries. They need to ensure
+         * however that {@link #applyPending(NodeEntryImpl)} properly invalidates the respective
+         * state when called.
+         *
+         * @param entry
+         * @param recursive  Invalidate state of child entries if <code>true</code>.
+         */
+        public void invalidate(NodeEntryImpl entry, boolean recursive);
+
+        /**
+         * Apply any pending {@link ItemState#invalidate() invalidation} of the underyling
+         * {@link ItemState} of this <code>entry</code>.
+         * @param entry
+         */
+        public void applyPending(NodeEntryImpl entry);
+    }
+
 }
\ No newline at end of file
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/HierarchyEntryImpl.java	(working copy)
@@ -26,9 +26,12 @@
 import org.apache.jackrabbit.jcr2spi.state.ItemState;
 import org.apache.jackrabbit.jcr2spi.state.ItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.Status;
+import org.apache.jackrabbit.jcr2spi.state.TransientItemStateFactory;
 import org.apache.jackrabbit.jcr2spi.state.ItemState.MergeResult;
+import org.apache.jackrabbit.spi.IdFactory;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
+import org.apache.jackrabbit.spi.PathFactory;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -76,6 +79,30 @@
     }
 
     /**
+     * Shortcut for {@link factory#getItemStateFactory()}
+     * @return
+     */
+    protected TransientItemStateFactory getItemStateFactory() {
+        return factory.getItemStateFactory();
+    }
+
+    /**
+     * Shortcut for {@link factory#getPathFactory()}
+     * @return
+     */
+    protected PathFactory getPathFactory() {
+        return factory.getPathFactory();
+    }
+
+    /**
+     * Shortcut for {@link factory#getIdFactory()}
+     * @return
+     */
+    protected IdFactory getIdFactory() {
+        return factory.getIdFactory();
+    }
+
+    /**
      * Resolves this <code>HierarchyEntryImpl</code> and returns the target
      * <code>ItemState</code> of this reference. This method may return a
      * cached <code>ItemState</code> if this method was called before already
@@ -239,12 +266,13 @@
      * @see HierarchyEntry#invalidate(boolean)
      */
     public void invalidate(boolean recursive) {
-        if (getStatus() == Status.EXISTING) {
-            ItemState state = internalGetItemState();
-            state.setStatus(Status.INVALIDATED);
-        } else {
-            log.debug("Skip invalidation for HierarchyEntry " + name + " with status " + Status.getName(getStatus()));
+        ItemState state = internalGetItemState();
+        if (state == null) {
+            log.debug("Skip invalidation for unresolved HierarchyEntry " + name);
         }
+        else {
+            state.invalidate();
+        }
     }
 
     /**
@@ -314,7 +342,7 @@
          * then be merged into the current state.
          */
         try {
-            ItemStateFactory isf = factory.getItemStateFactory();
+            ItemStateFactory isf = getItemStateFactory();
             if (denotesNode()) {
                 NodeEntry ne = (NodeEntry) this;
                 isf.createNodeState(ne.getWorkspaceId(), ne);
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/NodeEntryImpl.java	(working copy)
@@ -16,6 +16,22 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemExistsException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.PathNotFoundException;
+import javax.jcr.RepositoryException;
+
 import org.apache.commons.collections.iterators.IteratorChain;
 import org.apache.jackrabbit.commons.iterator.RangeIteratorAdapter;
 import org.apache.jackrabbit.jcr2spi.operation.AddNode;
@@ -47,21 +63,6 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.jcr.InvalidItemStateException;
-import javax.jcr.ItemExistsException;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.PathNotFoundException;
-import javax.jcr.RepositoryException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
 /**
  * <code>NodeEntryImpl</code> implements common functionality for child
  * node entry implementations.
@@ -114,6 +115,12 @@
     private RevertInfo revertInfo;
 
     /**
+     * Information regarding the invalidation status of the underyling {@link ItemState}
+     * of this entry. The semantics depend on the {@link InvalidationStrategy}.
+     */
+    private long invalidationState;
+
+    /**
      * Creates a new <code>NodeEntryImpl</code>
      *
      * @param parent    the <code>NodeEntry</code> that owns this child item
@@ -164,31 +171,22 @@
         return true;
     }
 
-    /**
-     * @inheritDoc
-     * @see HierarchyEntry#invalidate(boolean)
-     */
     public void invalidate(boolean recursive) {
-        if (recursive) {
-            // invalidate all child entries including properties present in the
-            // attic (removed props shadowed by a new property with the same name).
-            for (Iterator it = getAllChildEntries(true); it.hasNext();) {
-                HierarchyEntry ce = (HierarchyEntry) it.next();
-                ce.invalidate(recursive);
-            }
-        }
-        // invalidate 'childNodeEntries'
-        if (getStatus() != Status.NEW) {
-            childNodeEntries.invalidate();
-        }
-        // ... and invalidate the resolved state (if available)
-        super.invalidate(recursive);
+        getInvalidationStrategy().invalidate(this, recursive);
     }
 
+    public void calculateStatus() {
+        getInvalidationStrategy().applyPending(this);
+    }
+
+    private InvalidationStrategy getInvalidationStrategy() {
+        return factory.getInvalidationStrategy();
+    }
+
     /**
      * If 'recursive' is true, the complete hierarchy below this entry is
      * traversed and reloaded. Otherwise only this entry and the direct
-     * decendants are reloaded.
+     * descendants are reloaded.
      *
      * @see HierarchyEntry#reload(boolean)
      */
@@ -199,7 +197,7 @@
         // reload all children unless 'recursive' is false and the reload above
         // did not cause this entry to be removed -> therefore check status.
         if (recursive && !Status.isTerminal(getStatus())) {
-            // recursivly reload all entries including props that are in the attic.
+            // recursively reload all entries including props that are in the attic.
             for (Iterator it = getAllChildEntries(true); it.hasNext();) {
                 HierarchyEntry ce = (HierarchyEntry) it.next();
                 ce.reload(recursive);
@@ -300,11 +298,11 @@
      * @see NodeEntry#getId()
      */
     public NodeId getId() throws InvalidItemStateException, RepositoryException {
-        IdFactory idFactory = factory.getIdFactory();
+        IdFactory idFactory = getIdFactory();
         if (uniqueID != null) {
             return idFactory.createNodeId(uniqueID);
         } else {
-            PathFactory pf = factory.getPathFactory();
+            PathFactory pf = getPathFactory();
             if (parent == null) {
                 // root node
                 return idFactory.createNodeId((String) null, pf.getRootPath());
@@ -319,14 +317,15 @@
      * @see NodeEntry#getWorkspaceId()
      */
     public NodeId getWorkspaceId() throws InvalidItemStateException, RepositoryException {
-        IdFactory idFactory = factory.getIdFactory();
         if (uniqueID != null || parent == null) {
             // uniqueID and root-node -> internal id is always the same as getId().
             return getId();
         } else {
-            PathFactory pf = factory.getPathFactory();
-            NodeId parentId = (revertInfo != null) ? revertInfo.oldParent.getWorkspaceId() : parent.getWorkspaceId();
-            return idFactory.createNodeId(parentId, pf.create(getName(true), getIndex(true)));
+            NodeId parentId = (revertInfo != null)
+                ? revertInfo.oldParent.getWorkspaceId()
+                : parent.getWorkspaceId();
+            return getIdFactory().createNodeId(parentId,
+                    getPathFactory().create(getName(true), getIndex(true)));
         }
     }
 
@@ -375,7 +374,7 @@
         NodeEntryImpl entry = this;
         Path.Element[] elems = path.getElements();
         for (int i = 0; i < elems.length; i++) {
-            Path.Element elem = (Path.Element) elems[i];
+            Path.Element elem = elems[i];
             // check for root element
             if (elem.denotesRoot()) {
                 if (entry.getParent() != null) {
@@ -421,7 +420,7 @@
                 * 2) if the NameElement does not have SNS-index => try Property
                 * 3) else throw
                 */
-                PathBuilder pb = new PathBuilder(factory.getPathFactory());
+                PathBuilder pb = new PathBuilder(getPathFactory());
                 for (int j = i; j < elems.length; j++) {
                     pb.addLast(elems[j]);
                 }
@@ -450,7 +449,7 @@
         Path.Element[] elems = path.getElements();
         int i = 0;
         for (; i < elems.length-1; i++) {
-            Path.Element elem = (Path.Element) elems[i];
+            Path.Element elem = elems[i];
             if (elems[i].denotesRoot()) {
                 if (entry.getParent() != null) {
                     throw new RepositoryException("NodeEntry out of 'hierarchy' " + path.toString());
@@ -499,13 +498,13 @@
             * PropertyState (including building the itermediate entries. If that
             * fails ItemNotFoundException is thrown.
             */
-            PathBuilder pb = new PathBuilder(factory.getPathFactory());
+            PathBuilder pb = new PathBuilder(getPathFactory());
             for (int j = i; j < elems.length; j++) {
                 pb.addLast(elems[j]);
             }
             Path remainingPath = pb.getPath();
 
-            IdFactory idFactory = factory.getIdFactory();
+            IdFactory idFactory = getIdFactory();
             NodeId parentId = entry.getWorkspaceId();
             parentId = (remainingPath.getLength() == 1) ? parentId : idFactory.createNodeId(parentId, remainingPath.getAncestor(1));
             PropertyId propId = idFactory.createPropertyId(parentId, remainingPath.getNameElement().getName());
@@ -606,8 +605,9 @@
         if (cne == null && loadIfNotFound
                 && !containsAtticChild(entries, nodeName, index)
                 && !childNodeEntries.isComplete()) {
-            PathFactory pf = factory.getPathFactory();
-            NodeId cId = factory.getIdFactory().createNodeId(getWorkspaceId(), pf.create(nodeName, index));
+
+            NodeId cId = getIdFactory().createNodeId(getWorkspaceId(),
+                    getPathFactory().create(nodeName, index));
             cne = loadNodeEntry(cId);
         }
         return cne;
@@ -687,7 +687,7 @@
     public NodeEntry addNewNodeEntry(Name nodeName, String uniqueID,
                                      Name primaryNodeType, QNodeDefinition definition) throws RepositoryException {
         NodeEntry entry = internalAddNodeEntry(nodeName, uniqueID, Path.INDEX_UNDEFINED);
-        NodeState state = factory.getItemStateFactory().createNewNodeState(entry, primaryNodeType, definition);
+        NodeState state = getItemStateFactory().createNewNodeState(entry, primaryNodeType, definition);
         entry.setItemState(state);
         return entry;
     }
@@ -826,9 +826,8 @@
             }
         }
 
-        // add the property entry
         PropertyEntry entry = factory.createPropertyEntry(this, propName);
-        PropertyState state = factory.getItemStateFactory().createNewPropertyState(entry, definition, values, propertyType);
+        PropertyState state = getItemStateFactory().createNewPropertyState(entry, definition, values, propertyType);
         entry.setItemState(state);
 
         // add the property entry if creating the new state was successful
@@ -984,14 +983,14 @@
      * Returns a <code>NodeState</code>.
      */
     ItemState doResolve() throws ItemNotFoundException, RepositoryException {
-        return factory.getItemStateFactory().createNodeState(getWorkspaceId(), this);
+        return getItemStateFactory().createNodeState(getWorkspaceId(), this);
     }
 
     /**
      * @see HierarchyEntryImpl#buildPath(boolean)
      */
     Path buildPath(boolean wspPath) throws RepositoryException {
-        PathFactory pf = factory.getPathFactory();
+        PathFactory pf = getPathFactory();
         // shortcut for root state
         if (parent == null) {
             return pf.getRootPath();
@@ -1085,6 +1084,25 @@
     }
 
     /**
+     * Invalidates the underlying {@link ItemState}. If <code>recursive</code> is
+     * true also invalidates the underlying item states of all child entries.
+     * @param recursive
+     */
+    private void invalidateInternal(boolean recursive) {
+        if (recursive) {
+
+            // invalidate all child entries including properties present in the
+            // attic (removed props shadowed by a new property with the same name).
+            for (Iterator it = getAllChildEntries(true); it.hasNext();) {
+                HierarchyEntry ce = (HierarchyEntry) it.next();
+                ce.invalidate(true);
+            }
+        }
+
+        super.invalidate(true);
+    }
+
+    /**
      * @param oldName
      * @param oldIndex
      * @return <code>true</code> if the given oldName and oldIndex match
@@ -1143,7 +1161,7 @@
      */
     private NodeEntry loadNodeEntry(NodeId childId) throws RepositoryException {
         try {
-            NodeState state = factory.getItemStateFactory().createDeepNodeState(childId, this);
+            NodeState state = getItemStateFactory().createDeepNodeState(childId, this);
             return state.getNodeEntry();
         } catch (ItemNotFoundException e) {
             return null;
@@ -1159,7 +1177,7 @@
      */
     private PropertyEntry loadPropertyEntry(PropertyId childId) throws RepositoryException {
         try {
-            PropertyState state = factory.getItemStateFactory().createDeepPropertyState(childId, this);
+            PropertyState state = getItemStateFactory().createDeepPropertyState(childId, this);
             return (PropertyEntry) state.getHierarchyEntry();
         } catch (ItemNotFoundException e) {
             return null;
@@ -1479,6 +1497,7 @@
                     } // else: propEntry has never been moved to the attic (see 'addPropertyEntry')
                 }
                 rmEntry.revert();
+                break;
             default: // ignore
         }
 
@@ -1620,4 +1639,131 @@
             revertInfo = null;
         }
     }
+
+    // -----------------------------------------------------< InvalidationStrategy >---
+
+    /**
+     * An implementation of <code>InvalidationStrategy</code> which lazily invalidates
+     * the underlying {@link ItemState}s.
+     */
+    static class LazyInvalidation implements InvalidationStrategy {
+
+        /**
+         * Marker for entries with a pending recursive invalidation.
+         */
+        private static long INVALIDATION_PENDING = -1;
+
+        /**
+         * Time stamp of the last time a recursive invalidation occurred.
+         */
+        private long lastInvalidation;
+
+        /**
+         * A recursive invalidation is being processed if <code>true</code>.
+         * This flag is for preventing re-entrance.
+         */
+        private boolean invalidating;
+
+        /**
+         * Actual time stamp
+         */
+        private long timeStamp;
+
+        /**
+         * @return  time stamp used to mark entries
+         */
+        public long getTimeStamp() {
+            return timeStamp++;
+        }
+
+        /**
+         * Records a pending recursive {@link ItemState#invalidate() invalidation} for
+         * <code>entry</code> if <code>recursive</code> is <code>true</code>. Otherwise
+         * invalidates the entry right away.
+         * {@inheritDoc}
+         */
+        public void invalidate(NodeEntryImpl entry, boolean recursive) {
+            if (recursive) {
+                entry.invalidationState = INVALIDATION_PENDING;
+                if (!invalidating) {
+                    lastInvalidation = getTimeStamp();
+                }
+            }
+            else {
+                entry.invalidateInternal(false);
+            }
+        }
+
+        /**
+         * Checks whether <code>entry</code> itself has a invalidation pending. If so,
+         * {@link ItemState#invalidate()} <code>entry</code>. Otherwise check wheter an
+         * invalidation occurred after the entry has last been invalidated. If so,
+         * search the path to the root for an originator of the pending invalidation.
+         * If such an originator is found, invalidate each entry on the path. Otherwise
+         * do nothing.
+         * {@inheritDoc}
+         */
+        public void applyPending(NodeEntryImpl entry) {
+            if (!invalidating) {
+                invalidating = true;
+                try {
+                    if (entry.invalidationState == INVALIDATION_PENDING) {
+                        entry.invalidateInternal(true);
+                        entry.invalidationState = getTimeStamp();
+                    }
+                    else if (entry.invalidationState <= lastInvalidation) {
+                        invalidate(entry);
+                    }
+                }
+                finally {
+                    invalidating = false;
+                }
+            }
+        }
+
+        /**
+         * Search the path to the root for an originator of a pending invalidation of
+         * this <code>entry</code>. If such an originator is found, invalidate each
+         * entry on the path. Otherwise do nothing.
+         *
+         * @param entry
+         */
+        private void invalidate(NodeEntryImpl entry) {
+            if (entry != null) {
+                invalidate(entry.parent);
+                if (entry.invalidationState == INVALIDATION_PENDING) {
+                    entry.invalidateInternal(true);
+                }
+                entry.invalidationState = getTimeStamp();
+            }
+        }
+
+    }
+
+    /**
+     * An implementation of <code>InvalidationStrategy</code> which eagerly invalidates
+     * the underlying {@link ItemState}s.
+     */
+    static class EagerInvalidation implements InvalidationStrategy {
+
+        /**
+         * Calls {@link ItemState#invalidate()} for the underlying item state of this
+         * <code>entry</code> and - if <code>recursive</code> is <code>true</code> -
+         * recursively for all item states of all child entries
+         * {@inheritDoc}
+         */
+        public void invalidate(NodeEntryImpl entry, boolean recursive) {
+            entry.invalidateInternal(recursive);
+        }
+
+        /**
+         * Does nothing since invalidation has occurred already.
+         * {@inheritDoc}
+         */
+        public void applyPending(NodeEntryImpl entry) {
+            // Empty
+        }
+
+    }
+
 }
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/hierarchy/PropertyEntryImpl.java	(working copy)
@@ -16,19 +16,19 @@
  */
 package org.apache.jackrabbit.jcr2spi.hierarchy;
 
+import javax.jcr.InvalidItemStateException;
+import javax.jcr.ItemNotFoundException;
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.jcr2spi.operation.Operation;
+import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
+import org.apache.jackrabbit.jcr2spi.state.ItemState;
+import org.apache.jackrabbit.jcr2spi.state.PropertyState;
+import org.apache.jackrabbit.jcr2spi.state.Status;
 import org.apache.jackrabbit.spi.Name;
 import org.apache.jackrabbit.spi.Path;
 import org.apache.jackrabbit.spi.PropertyId;
-import org.apache.jackrabbit.jcr2spi.state.PropertyState;
-import org.apache.jackrabbit.jcr2spi.state.ItemState;
-import org.apache.jackrabbit.jcr2spi.state.Status;
-import org.apache.jackrabbit.jcr2spi.operation.Operation;
-import org.apache.jackrabbit.jcr2spi.operation.SetPropertyValue;
 
-import javax.jcr.RepositoryException;
-import javax.jcr.ItemNotFoundException;
-import javax.jcr.InvalidItemStateException;
-
 /**
  * <code>PropertyEntryImpl</code> implements a reference to a property state.
  */
@@ -66,7 +66,7 @@
      * Returns a <code>PropertyState</code>.
      */
     ItemState doResolve() throws ItemNotFoundException, RepositoryException {
-        return factory.getItemStateFactory().createPropertyState(getWorkspaceId(), this);
+        return getItemStateFactory().createPropertyState(getWorkspaceId(), this);
     }
 
     /**
@@ -74,7 +74,7 @@
      */
     Path buildPath(boolean workspacePath) throws RepositoryException {
         Path parentPath = parent.buildPath(workspacePath);
-        return factory.getPathFactory().create(parentPath, getName(), true);
+        return getPathFactory().create(parentPath, getName(), true);
     }
 
     //------------------------------------------------------< PropertyEntry >---
@@ -82,14 +82,14 @@
      * @see PropertyEntry#getId()
      */
     public PropertyId getId() throws InvalidItemStateException, RepositoryException {
-        return factory.getIdFactory().createPropertyId(parent.getId(), getName());
+        return getIdFactory().createPropertyId(parent.getId(), getName());
     }
 
     /**
      * @see PropertyEntry#getWorkspaceId()
      */
     public PropertyId getWorkspaceId() throws InvalidItemStateException, RepositoryException {
-        return factory.getIdFactory().createPropertyId(parent.getWorkspaceId(), getName());
+        return getIdFactory().createPropertyId(parent.getWorkspaceId(), getName());
     }
 
     /**
@@ -136,4 +136,9 @@
                 // ignore
         }
     }
+
+    public void calculateStatus() {
+        // empty
+    }
+
 }
Index: jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java
===================================================================
--- jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java	(revision 757735)
+++ jackrabbit-jcr2spi/src/main/java/org/apache/jackrabbit/jcr2spi/state/ItemState.java	(working copy)
@@ -215,6 +215,7 @@
      * @return the status of this item.
      */
     public final int getStatus() {
+        getHierarchyEntry().calculateStatus();
         return status;
     }
 
@@ -309,6 +310,19 @@
     }
 
     /**
+     * Invalidates this state: set its {@link Status} to {@link Status#INVALIDATED}
+     * if the current status is {@link Status#EXISTING}. Does nothing otherwise.
+     */
+    public void invalidate() {
+        if (status == Status.EXISTING) {
+            setStatus(Status.INVALIDATED);
+        }
+        else {
+            log.debug("Skip invalidation for item {} with status {}", getName(), Status.getName(status));
+        }
+    }
+
+    /**
      * Marks this item state as modified.
      */
     void markModified() throws InvalidItemStateException {
