Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/IterablePersistenceManager.java	(revision )
@@ -17,16 +17,18 @@
 package org.apache.jackrabbit.core.persistence;
 
 import java.util.List;
+import java.util.Map;
 
 import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.state.ItemStateException;
 
 import javax.jcr.RepositoryException;
 
 /**
- * The iterable persistence manager can return the list of node ids that are stored.
+ * The iterable persistence manager can return the list of node ids and node infos that are stored.
  * Possible applications are backup, migration (copying a workspace or repository),
- * and data store garbage collection.
+ * data store garbage collection, and consistency checking.
  */
 public interface IterablePersistenceManager extends PersistenceManager {
 
@@ -39,11 +41,28 @@
      *
      * @param after the lower limit, or null for no limit.
      * @param maxCount the maximum number of node ids to return, or 0 for no limit.
-     * @return a list of all bundles.
+     * @return a list of all node ids.
      * @throws ItemStateException if an error while loading occurs.
-     * @throws RepositoryException if a repository exception occurs
+     * @throws RepositoryException if a repository exception occurs.
      */
     List<NodeId> getAllNodeIds(NodeId after, int maxCount)
+            throws ItemStateException, RepositoryException;
+
+
+    /**
+     * Get all {@link NodeInfo}s.
+     * A typical application will call this method multiple time, where 'after'
+     * is the las row read. The maxCount parameter defines the maximum number of
+     * node ids returned, 0 meaning no limit. The order of the node ids is specific for the
+     * given persistence manager. Items that are added concurrently may not be included.
+     *
+     * @param after the lower limit, or null for no limit.
+     * @param maxCount the maximum number of node infos to return, or 0 for no limit.
+     * @return a list of all node infos.
+     * @throws ItemStateException if an error while loading occurs.
+     * @throws RepositoryException if a repository exception occurs.
+     */
+    Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount)
             throws ItemStateException, RepositoryException;
 
 }
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java	(revision )
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/util/NodeInfo.java	(revision )
@@ -0,0 +1,160 @@
+/*
+ * 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.core.persistence.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+import javax.jcr.PropertyType;
+
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.spi.Name;
+
+/**
+ * Holds purely structural information about a node. Used by the consistency checker.
+ */
+public final class NodeInfo {
+
+    private static final ConcurrentMap<NodeId,NodeId> nodeIdPool = new ConcurrentHashMap<NodeId, NodeId>(1000);
+
+    /**
+     * The node id
+     */
+    private final NodeId nodeId;
+
+    /**
+     * The parent node id
+     */
+    private final NodeId parentId;
+
+    /**
+     * The child ids
+     */
+    private List<NodeId> children;
+
+    /**
+     * Map of reference property names of this node with their node id values
+     */
+    private Map<Name, List<NodeId>> references;
+
+    /**
+     * Whether this node is referenceable or not
+     */
+    private boolean isReferenceable;
+
+    /**
+     * Create a new NodeInfo object from a bundle
+     *
+     * @param bundle the node bundle
+     */
+    public NodeInfo(final NodePropBundle bundle) {
+        nodeId = getNodeId(bundle.getId());
+        parentId = getNodeId(bundle.getParentId());
+
+        List<NodePropBundle.ChildNodeEntry> childNodeEntries = bundle.getChildNodeEntries();
+        if (!childNodeEntries.isEmpty()) {
+            children = new ArrayList<NodeId>(childNodeEntries.size());
+            for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                children.add(getNodeId(entry.getId()));
+            }
+        } else {
+            children = Collections.emptyList();
+        }
+
+        for (NodePropBundle.PropertyEntry entry : bundle.getPropertyEntries()) {
+            if (entry.getType() == PropertyType.REFERENCE) {
+                if (references == null) {
+                    references = new HashMap<Name, List<NodeId>>(4);
+                }
+                List<NodeId> values = new ArrayList<NodeId>(entry.getValues().length);
+                for (InternalValue value : entry.getValues()) {
+                    values.add(getNodeId(value.getNodeId()));
+                }
+                references.put(entry.getName(), values);
+            }
+        }
+
+        if (references == null) {
+            references = Collections.emptyMap();
+        }
+        isReferenceable = bundle.isReferenceable();
+    }
+
+    /**
+     * @return the node id of this node
+     */
+    public NodeId getId() {
+        return nodeId;
+    }
+
+    /**
+     * @return the parent id of this node
+     */
+    public NodeId getParentId() {
+        return parentId;
+    }
+
+    /**
+     * @return the child ids of this node
+     */
+    public List<NodeId> getChildren() {
+        return children;
+    }
+
+    /**
+     * @return the reference properties along with their node id values of this node
+     */
+    public Map<Name, List<NodeId>> getReferences() {
+        return references;
+    }
+
+    /**
+     * @return whether the node represented by this node info is referenceable
+     */
+    public boolean isReferenceable() {
+        return isReferenceable;
+    }
+
+    /**
+     * Simple pool implementation to minimize memory overhead from node id objects
+     * @param nodeId  node id to cache
+     * @return  the cached node id
+     */
+    private static NodeId getNodeId(NodeId nodeId) {
+        if (nodeId == null) {
+            return null;
+        }
+        NodeId cached = nodeIdPool.get(nodeId);
+        if (cached == null) {
+            cached = nodeIdPool.putIfAbsent(nodeId, nodeId);
+            if (cached == null) {
+                cached = nodeId;
+            }
+        }
+        return cached;
+    }
+
+    public static void clearPool() {
+        nodeIdPool.clear();
+    }
+}
Index: jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java	(revision )
+++ jackrabbit-core/src/test/java/org/apache/jackrabbit/core/data/ConsistencyCheckerImplTest.java	(revision )
@@ -0,0 +1,364 @@
+/*
+ * 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.core.data;
+
+import java.util.Arrays;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.RepositoryException;
+
+import org.apache.jackrabbit.core.id.NodeId;
+import org.apache.jackrabbit.core.persistence.bundle.AbstractBundlePersistenceManager;
+import org.apache.jackrabbit.core.persistence.bundle.ConsistencyCheckerImpl;
+import org.apache.jackrabbit.core.persistence.check.ConsistencyReport;
+import org.apache.jackrabbit.core.persistence.check.ReportItem;
+import org.apache.jackrabbit.core.persistence.util.BLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
+import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.NoSuchItemStateException;
+import org.apache.jackrabbit.core.state.NodeReferences;
+import org.apache.jackrabbit.spi.Name;
+import org.apache.jackrabbit.spi.commons.name.NameConstants;
+import org.apache.jackrabbit.spi.commons.name.NameFactoryImpl;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import junit.framework.TestCase;
+
+public class ConsistencyCheckerImplTest extends TestCase {
+
+    private static final Logger log = LoggerFactory.getLogger(ConsistencyCheckerImplTest.class);
+
+    private static final Name testName = NameFactoryImpl.getInstance().create("", "test");
+
+    // Abandoned nodes are nodes that have a link to a parent but that
+    // parent does not have a link back to the child
+    public void testFixAbandonedNode() throws RepositoryException {
+        NodePropBundle root = new NodePropBundle(new NodeId("cafebabe-cafe-babe-cafe-babecafebabe"));
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+
+        root.addChildNodeEntry(testName, bundle1.getId());
+        bundle1.setParentId(root.getId());
+
+        // node2 has a reference to node 1 as its parent, but node1 doesn't have
+        // a corresponding child node entry
+        bundle2.setParentId(bundle1.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(root, bundle1, bundle2));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null);
+
+        // run checker with fix = true
+        ConsistencyReport report = checker.check(null, false, true, null);
+
+//        printReport(report);
+        
+        // node1 should now have a child node entry for node2
+        bundle1 = pm.loadBundle(bundle1.getId());
+        assertEquals(1, bundle1.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle1.getChildNodeEntries().get(0).getId());
+        
+        assertEquals(3, report.getNodeCount());
+        assertEquals(2, report.getItems().size());
+        final Iterator<ReportItem> items = report.getItems().iterator();
+        ReportItem reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ROOT, reportItem.getCheckerEvent());
+        reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ABANDONED, reportItem.getCheckerEvent());
+        assertEquals(bundle2.getId().toString(), reportItem.getNodeId());
+        assertEquals(bundle1.getId().toString(), reportItem.getObjectNodeId());
+        
+    }
+
+    /*
+     * There was a bug where when there were multiple abandoned nodes by the same parent
+     * only one of them was fixed. Hence this separate test case for this scenario.
+     */
+    public void testFixMultipleAbandonedNodesBySameParent() throws RepositoryException {
+        NodePropBundle root = new NodePropBundle(new NodeId("cafebabe-cafe-babe-cafe-babecafebabe"));
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 0));
+
+        root.addChildNodeEntry(testName, bundle1.getId());
+        bundle1.setParentId(root.getId());
+
+        // node2 and node3 have a reference to node1 as its parent, but node1 doesn't have
+        // corresponding child node entries
+        bundle2.setParentId(bundle1.getId());
+        bundle3.setParentId(bundle1.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(root, bundle1, bundle2, bundle3));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null);
+
+        // run checker with fix = true
+        ConsistencyReport report = checker.check(null, false, true, null);
+
+//        printReport(report);
+
+        // node1 should now have child node entries for node2 and node3
+        bundle1 = pm.loadBundle(bundle1.getId());
+        assertEquals(2, bundle1.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle1.getChildNodeEntries().get(0).getId());
+        assertEquals(bundle3.getId(), bundle1.getChildNodeEntries().get(1).getId());
+
+        assertEquals(4, report.getNodeCount());
+        assertEquals(3, report.getItems().size());
+
+        Iterator<ReportItem> items = report.getItems().iterator();
+        ReportItem reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ROOT, reportItem.getCheckerEvent());
+        reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ABANDONED, reportItem.getCheckerEvent());
+        assertEquals(bundle2.getId().toString(), reportItem.getNodeId());
+        assertEquals(bundle1.getId().toString(), reportItem.getObjectNodeId());
+        reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ABANDONED, reportItem.getCheckerEvent());
+        assertEquals(bundle3.getId().toString(), reportItem.getNodeId());
+        assertEquals(bundle1.getId().toString(), reportItem.getObjectNodeId());
+    }
+
+    // Orphaned nodes are those nodes who's parent does not exist
+    public void testAddOrphanedNodeToLostAndFound() throws RepositoryException {
+        NodePropBundle root = new NodePropBundle(new NodeId("cafebabe-cafe-babe-cafe-babecafebabe"));
+        NodePropBundle lostAndFound = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle orphaned = new NodePropBundle(new NodeId(0, 1));
+
+        root.addChildNodeEntry(testName, lostAndFound.getId());
+        lostAndFound.setParentId(root.getId());
+
+        // lost and found must be of type nt:unstructured
+        lostAndFound.setNodeTypeName(NameConstants.NT_UNSTRUCTURED);
+
+        // set non-existent parent node id
+        orphaned.setParentId(new NodeId(1, 0));
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(root, lostAndFound, orphaned));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null);
+
+        // run checker with fix = true
+        ConsistencyReport report = checker.check(null, false, true, lostAndFound.getId().toString());
+        
+//        printReport(report);
+
+        assertEquals(3, report.getNodeCount());
+        assertEquals(2, report.getItems().size());
+
+        // orphan should have been added to lost+found
+        lostAndFound = pm.loadBundle(lostAndFound.getId());
+        assertEquals(1, lostAndFound.getChildNodeEntries().size());
+        assertEquals(orphaned.getId(), lostAndFound.getChildNodeEntries().get(0).getId());
+
+        orphaned = pm.loadBundle(orphaned.getId());
+        assertEquals(lostAndFound.getId(), orphaned.getParentId());
+
+        Iterator<ReportItem> items = report.getItems().iterator();
+        ReportItem reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ROOT, reportItem.getCheckerEvent());
+        reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ORPHAN, reportItem.getCheckerEvent());
+        assertEquals(orphaned.getId().toString(), reportItem.getNodeId());
+        assertEquals(new NodeId(1, 0).toString(), reportItem.getObjectNodeId());
+    }
+
+    // Disconnected nodes are those nodes for which there are nodes
+    // that have the node as its child, but the node itself does not
+    // have those nodes as its parent
+    public void testFixDisconnectedNode() throws RepositoryException {
+        NodePropBundle root = new NodePropBundle(new NodeId("cafebabe-cafe-babe-cafe-babecafebabe"));
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 0));
+
+        root.addChildNodeEntry(testName, bundle1.getId());
+        root.addChildNodeEntry(testName, bundle2.getId());
+        bundle1.setParentId(root.getId());
+        bundle2.setParentId(root.getId());
+
+        // node1 has child node3
+        bundle1.addChildNodeEntry(testName, bundle3.getId());
+        // node2 also has child node3
+        bundle2.addChildNodeEntry(testName, bundle3.getId());
+        // node3 has node2 as parent
+        bundle3.setParentId(bundle2.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(root, bundle1, bundle2, bundle3));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null);
+
+        // run checker with fix = true
+        ConsistencyReport report = checker.check(null, false, true, null);
+
+        bundle1 = pm.loadBundle(bundle1.getId());
+        bundle2 = pm.loadBundle(bundle2.getId());
+        bundle3 = pm.loadBundle(bundle3.getId());
+
+        // node3 should have been removed as child node entry of node1
+        assertEquals(0, bundle1.getChildNodeEntries().size());
+
+        // node3 should still be a child of node2
+        assertEquals(1, bundle2.getChildNodeEntries().size());
+        assertEquals(bundle2.getId(), bundle3.getParentId());
+
+        assertEquals(4, report.getNodeCount());
+//        printReport(report);
+        assertEquals(2, report.getItems().size());
+
+        Iterator<ReportItem> items = report.getItems().iterator();
+        ReportItem reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.ROOT, reportItem.getCheckerEvent());
+        reportItem = items.next();
+        assertEquals(ReportItem.CheckerEvent.DISCONNECTED, reportItem.getCheckerEvent());
+        assertEquals(bundle1.getId().toString(), reportItem.getNodeId());
+    }
+
+    // make sure we don't fix anything in check mode, we can't be careful enough
+    public void testDontFixInCheckMode() throws RepositoryException {
+        NodePropBundle root = new NodePropBundle(new NodeId("cafebabe-cafe-babe-cafe-babecafebabe"));
+
+        /** abandoned node, also see {@link #testFixAbandonedNode} */
+        NodePropBundle bundle1 = new NodePropBundle(new NodeId(0, 0));
+        root.addChildNodeEntry(testName, bundle1.getId());
+        bundle1.setParentId(root.getId());
+        NodePropBundle bundle2 = new NodePropBundle(new NodeId(0, 1));
+        bundle2.setParentId(bundle1.getId());
+
+        /** orphaned node, also see {@link #testAddOrphanedNodeToLostAndFound} */
+        NodePropBundle lostAndFound = new NodePropBundle(new NodeId(1, 0));
+        root.addChildNodeEntry(testName, lostAndFound.getId());
+        lostAndFound.setParentId(root.getId());
+        lostAndFound.setNodeTypeName(NameConstants.NT_UNSTRUCTURED);
+        NodePropBundle orphaned = new NodePropBundle(new NodeId(1, 1));
+        orphaned.setParentId(new NodeId(0, 2));
+
+        /** disconnected node, also see {@link #testFixDisconnectedNode} */
+        NodePropBundle bundle3 = new NodePropBundle(new NodeId(1, 2));
+        NodePropBundle bundle4 = new NodePropBundle(new NodeId(2, 2));
+        NodePropBundle bundle5 = new NodePropBundle(new NodeId(0, 3));
+        root.addChildNodeEntry(testName, bundle3.getId());
+        bundle3.setParentId(root.getId());
+        root.addChildNodeEntry(testName, bundle4.getId());
+        bundle4.setParentId(root.getId());
+        bundle3.addChildNodeEntry(testName, bundle5.getId());
+        bundle4.addChildNodeEntry(testName, bundle5.getId());
+        bundle5.setParentId(bundle4.getId());
+
+        MockPersistenceManager pm = new MockPersistenceManager(Arrays.asList(root, bundle1, bundle2, bundle3, bundle4, bundle5, lostAndFound, orphaned));
+        ConsistencyCheckerImpl checker = new ConsistencyCheckerImpl(pm, null);
+
+        // run checker with fix = false
+        checker.check(null, false, false, null);
+
+        bundle1 = pm.loadBundle(bundle1.getId());
+        lostAndFound = pm.loadBundle(lostAndFound.getId());
+        bundle3 = pm.loadBundle(bundle3.getId());
+
+        // abandoned node should not have been un-abandoned
+        assertEquals(0, bundle1.getChildNodeEntries().size());
+
+        // orphan should not have been added to lost and found
+        assertEquals(0, lostAndFound.getChildNodeEntries().size());
+
+        // disconnected entries should not have been removed
+        assertEquals(1, bundle3.getChildNodeEntries().size());
+
+    }
+    
+    private void printReport(ConsistencyReport report) {
+        info(report.getNodeCount() + " nodes checked");
+        info(report.getItems().size()-1 + " problems found");
+        for (ReportItem item : report.getItems()) {
+            info(item.getMessage());
+        }
+    }
+    
+    private void info(String message) {
+        log.info(message);
+    }
+    
+    private static class MockPersistenceManager extends AbstractBundlePersistenceManager {
+
+        private Map<NodeId, NodePropBundle> bundles = new LinkedHashMap<NodeId, NodePropBundle>();
+
+        private MockPersistenceManager(List<NodePropBundle> bundles) {
+            for (NodePropBundle bundle : bundles) {
+                this.bundles.put(bundle.getId(), bundle);
+            }
+        }
+
+        public List<NodeId> getAllNodeIds(final NodeId after, final int maxCount) throws ItemStateException, RepositoryException {
+            return null;
+        }
+
+        public Map<NodeId, NodeInfo> getAllNodeInfos(final NodeId after, final int maxCount) throws ItemStateException, RepositoryException {
+            Map<NodeId, NodeInfo> allNodeInfos = new LinkedHashMap<NodeId, NodeInfo>();
+            boolean add = after == null;
+            for (Map.Entry<NodeId, NodePropBundle> entry : bundles.entrySet()) {
+                if (add) {
+                    allNodeInfos.put(entry.getKey(), new NodeInfo(entry.getValue()));
+                }
+                if (!add) {
+                    add = entry.getKey().equals(after);
+                }
+            }
+            return allNodeInfos;
+        }
+
+        @Override
+        protected NodePropBundle loadBundle(final NodeId id) {
+            return bundles.get(id);
+        }
+
+        @Override
+        protected void evictBundle(final NodeId id) {
+        }
+
+        @Override
+        protected void storeBundle(final NodePropBundle bundle) throws ItemStateException {
+            bundles.put(bundle.getId(), bundle);
+        }
+
+        @Override
+        protected void destroyBundle(final NodePropBundle bundle) throws ItemStateException {
+            bundles.remove(bundle.getId());
+        }
+
+        @Override
+        protected void destroy(final NodeReferences refs) throws ItemStateException {
+        }
+
+        @Override
+        protected void store(final NodeReferences refs) throws ItemStateException {
+        }
+
+        @Override
+        protected BLOBStore getBlobStore() {
+            return null;
+        }
+
+        public NodeReferences loadReferencesTo(final NodeId id) throws NoSuchItemStateException, ItemStateException {
+            return null;
+        }
+
+        public boolean existsReferencesTo(final NodeId targetId) throws ItemStateException {
+            return false;
+        }
+    }
+}
\ No newline at end of file
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItemImpl.java	(revision )
@@ -16,26 +16,39 @@
  */
 package org.apache.jackrabbit.core.persistence.check;
 
-public class ReportItemImpl implements ReportItem {
+public final class ReportItemImpl implements ReportItem {
 
     private final String nodeId;
+    private final String objectNodeId;
     private final String message;
+    private final CheckerEvent checkerEvent;
 
-    public ReportItemImpl(String nodeId, String message) {
+    public ReportItemImpl(String nodeId, String objectNodeId, String message, CheckerEvent checkerEvent) {
         this.nodeId = nodeId;
         this.message = message;
+        this.checkerEvent = checkerEvent;
+        this.objectNodeId = objectNodeId;
     }
-
+    
     public String getNodeId() {
         return nodeId;
     }
 
+    public String getObjectNodeId() {
+        return objectNodeId;
+    }
+
     public String getMessage() {
         return message;
     }
 
+    public CheckerEvent getCheckerEvent() {
+        return checkerEvent;
+    }
+
     @Override
     public String toString() {
         return nodeId + " -- " + message;
     }
+
 }
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/pool/BundleDbPersistenceManager.java	(revision )
@@ -26,7 +26,9 @@
 import java.sql.SQLException;
 import java.sql.Types;
 import java.util.ArrayList;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
 import javax.jcr.RepositoryException;
 import javax.sql.DataSource;
@@ -43,6 +45,7 @@
 import org.apache.jackrabbit.core.persistence.util.BundleBinding;
 import org.apache.jackrabbit.core.persistence.util.ErrorHandling;
 import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.persistence.util.Serializer;
 import org.apache.jackrabbit.core.state.ChangeLog;
@@ -141,6 +144,8 @@
     protected String bundleDeleteSQL;
     protected String bundleSelectAllIdsFromSQL;
     protected String bundleSelectAllIdsSQL;
+    protected String bundleSelectAllBundlesFromSQL;
+    protected String bundleSelectAllBundlesSQL;
 
     // SQL statements for NodeReference management
     protected String nodeReferenceInsertSQL;
@@ -820,6 +825,58 @@
     /**
      * {@inheritDoc}
      */
+    public synchronized Map<NodeId, NodeInfo> getAllNodeInfos(NodeId bigger, int maxCount) throws ItemStateException {
+        ResultSet rs = null;
+        try {
+            String sql = bundleSelectAllBundlesSQL;
+            NodeId lowId = null;
+            Object[] keys = new Object[0];
+            if (bigger != null) {
+                sql = bundleSelectAllBundlesFromSQL;
+                lowId = bigger;
+                keys = getKey(bigger);
+            }
+            if (getStorageModel() == SM_LONGLONG_KEYS && maxCount > 0) {
+                // get some more rows, in case the first row is smaller
+                // only required for SM_LONGLONG_KEYS
+                // probability is very low to get get the wrong first key, < 1 : 2^64
+                // see also bundleSelectAllIdsFrom SQL statement
+                maxCount += 10;
+            }
+            rs = conHelper.exec(sql, keys, false, maxCount);
+            Map<NodeId, NodeInfo> result = new LinkedHashMap<NodeId, NodeInfo>(maxCount);
+            while ((maxCount == 0 || result.size() < maxCount) && rs.next()) {
+                NodeId current;
+                if (getStorageModel() == SM_BINARY_KEYS) {
+                    current = new NodeId(rs.getBytes(1));
+                } else {
+                    long high = rs.getLong(1);
+                    long low = rs.getLong(2);
+                    current = new NodeId(high, low);
+                }
+                if (getStorageModel() == SM_LONGLONG_KEYS && lowId != null) {
+                    // skip the keys that are smaller or equal (see above, maxCount += 10)
+                    if (current.compareTo(lowId) <= 0) {
+                        continue;
+                    }
+                }
+                NodePropBundle bundle = readBundle(current, rs, getStorageModel() == SM_LONGLONG_KEYS ? 3 : 2);
+                NodeInfo nodeInfo = new NodeInfo(bundle);
+                result.put(nodeInfo.getId(), nodeInfo);
+            }
+            return result;
+        } catch (SQLException e) {
+            String msg = "getAllNodeIds failed.";
+            log.error(msg, e);
+            throw new ItemStateException(msg, e);
+        } finally {
+            DbUtility.close(rs);
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
     @Override
     protected NodePropBundle loadBundle(NodeId id) throws ItemStateException {
         try {
@@ -1059,6 +1116,8 @@
 
             bundleSelectAllIdsSQL = "select NODE_ID from " + schemaObjectPrefix + "BUNDLE ORDER BY NODE_ID";
             bundleSelectAllIdsFromSQL = "select NODE_ID from " + schemaObjectPrefix + "BUNDLE WHERE NODE_ID > ? ORDER BY NODE_ID";
+            bundleSelectAllBundlesSQL = "select NODE_ID, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE ORDER BY NODE_ID";
+            bundleSelectAllBundlesFromSQL = "select NODE_ID, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE WHERE NODE_ID > ? ORDER BY NODE_ID";
         } else {
             bundleInsertSQL = "insert into " + schemaObjectPrefix + "BUNDLE (BUNDLE_DATA, NODE_ID_HI, NODE_ID_LO) values (?, ?, ?)";
             bundleUpdateSQL = "update " + schemaObjectPrefix + "BUNDLE set BUNDLE_DATA = ? where NODE_ID_HI = ? and NODE_ID_LO = ?";
@@ -1082,6 +1141,16 @@
                 "select NODE_ID_HI, NODE_ID_LO from " + schemaObjectPrefix + "BUNDLE"
                 + " WHERE (NODE_ID_HI >= ?) AND (? IS NOT NULL)"
                 + " ORDER BY NODE_ID_HI, NODE_ID_LO";
+
+            bundleSelectAllBundlesSQL = "select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from " + schemaObjectPrefix
+                    + "BUNDLE ORDER BY NODE_ID_HI, NODE_ID_LO";
+            // need to use HI and LO parameters
+            // this is not the exact statement, but not all databases support WHERE (NODE_ID_HI, NODE_ID_LOW) >= (?, ?)
+            bundleSelectAllBundlesFromSQL =
+                    "select NODE_ID_HI, NODE_ID_LO, BUNDLE_DATA from " + schemaObjectPrefix + "BUNDLE"
+                            + " WHERE (NODE_ID_HI >= ?) AND (? IS NOT NULL)"
+                            + " ORDER BY NODE_ID_HI, NODE_ID_LO";
+
         }
 
     }
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/check/ReportItem.java	(revision )
@@ -17,17 +17,77 @@
 package org.apache.jackrabbit.core.persistence.check;
 
 /**
- * An item reported inside a {@link ConsistencyChecker.Report}.
+ * An item reported inside a {@link ConsistencyReport}.
  */
 public interface ReportItem {
 
+    public enum CheckerEvent {
+
-    /**
+        /**
-     * @return node id to which the message applies
+         * Orphan: Parent of this node does not exist
-     */
+         */
+        ORPHAN,
+
+        /**
+         * Disconnected: this node has a child node entry of a node
+         * that defines a different parent than this node
+         */
+        DISCONNECTED,
+
+        /**
+         * Abandoned: parent has no child node entry for this node
+         */
+        ABANDONED,
+
+        /**
+         * Missing: node has missing children
+         */
+        MISSING,
+
+        /**
+         * Reference: node has broken references
+         */
+        REFERENCE,
+
+        /**
+         * An exception occurred during checking
+         */
+        EXCEPTION,
+
+        /**
+         * Root: node is a root node
+         */
+        ROOT,
+
+        /**
+         * Virtual: node is a virtual node
+         */
+        VIRTUAL
+    }
+
+    /**
+     * @return the subject node id to which this report item applies
+     */
     public String getNodeId();
 
     /**
+     * The object of a report item is either the broken child (in the case of
+     * missing or disconnected type event), the broken parent (in the case of
+     * an orphan or abandoned type event), or the reference (in the case of
+     * reference type event). Null in the case of exception type event.
+     *
+     * @return  the object node id to which this report item applies or null
+     */
+    public String getObjectNodeId();
+
+    /**
      * @return message
      */
     public String getMessage();
+
+    /**
+     * @return type of inconsistency
+     */
+    public CheckerEvent getCheckerEvent();
+
 }
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/mem/InMemBundlePersistenceManager.java	(revision )
@@ -27,6 +27,7 @@
 import java.io.InputStream;
 import java.util.ArrayList;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -44,6 +45,7 @@
 import org.apache.jackrabbit.core.persistence.util.BundleBinding;
 import org.apache.jackrabbit.core.persistence.util.ErrorHandling;
 import org.apache.jackrabbit.core.persistence.util.FileSystemBLOBStore;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.persistence.util.Serializer;
 import org.apache.jackrabbit.core.state.ItemStateException;
@@ -495,6 +497,18 @@
         // ignore after and count parameters.
         List<NodeId> result = new ArrayList<NodeId>();
         result.addAll(bundleStore.keySet());
+        return result;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount) throws ItemStateException, RepositoryException {
+        List<NodeId> nodeIds = getAllNodeIds(after, maxCount);
+        Map<NodeId, NodeInfo> result = new LinkedHashMap<NodeId, NodeInfo>(nodeIds.size());
+        for (NodeId nodeId : nodeIds) {
+            result.put(nodeId, new NodeInfo(loadBundle(nodeId)));
+        }
         return result;
     }
 
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/ConsistencyCheckerImpl.java	(revision )
@@ -18,8 +18,10 @@
 
 import java.util.ArrayList;
 import java.util.Collection;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.LinkedHashSet;
 import java.util.List;
+import java.util.Map;
 import java.util.Random;
 import java.util.Set;
 
@@ -31,6 +33,7 @@
 import org.apache.jackrabbit.core.persistence.check.ConsistencyReportImpl;
 import org.apache.jackrabbit.core.persistence.check.ReportItem;
 import org.apache.jackrabbit.core.persistence.check.ReportItemImpl;
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.apache.jackrabbit.core.persistence.util.NodePropBundle;
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.spi.NameFactory;
@@ -39,11 +42,11 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+
 public class ConsistencyCheckerImpl {
 
     /** the default logger */
-    private static Logger log = LoggerFactory
-            .getLogger(ConsistencyCheckerImpl.class);
+    private static Logger log = LoggerFactory.getLogger(ConsistencyCheckerImpl.class);
 
     private final AbstractBundlePersistenceManager pm;
 
@@ -51,9 +54,13 @@
 
     private static final NameFactory NF = NameFactoryImpl.getInstance();
 
-    // process 64K nodes at once
-    private static int NODESATONCE = 1024 * 64;
+    // load 64K node infos at once by default
+    private static final int NODESATONCE = Integer.getInteger("org.apache.jackrabbit.checker.nodesatonce", 1024 * 8);
 
+    // whether to load all node infos before checking or to check nodes as they are loaded
+    private static final boolean CHECKAFTERLOADING = Boolean.getBoolean("org.apache.jackrabbit.checker.checkafterloading");
+
+
     public ConsistencyCheckerImpl(AbstractBundlePersistenceManager pm,
             ConsistencyCheckListener listener) {
         this.pm = pm;
@@ -62,11 +69,10 @@
 
     public ConsistencyReport check(String[] uuids, boolean recursive,
             boolean fix, String lostNFoundId) throws RepositoryException {
-        Set<ReportItem> reports = new HashSet<ReportItem>();
+        Set<ReportItem> reports = new LinkedHashSet<ReportItem>();
 
         long tstart = System.currentTimeMillis();
-        int total = internalCheckConsistency(uuids, recursive, fix, reports,
-                lostNFoundId);
+        int total = internalCheckConsistency(uuids, recursive, fix, reports, lostNFoundId);
         long elapsed = System.currentTimeMillis() - tstart;
 
         return new ConsistencyReportImpl(total, elapsed, reports);
@@ -76,66 +82,72 @@
             boolean fix, Set<ReportItem> reports, String lostNFoundId)
             throws RepositoryException {
         int count = 0;
-        Collection<NodePropBundle> modifications = new ArrayList<NodePropBundle>();
-        Set<NodeId> orphaned = new HashSet<NodeId>();
 
         NodeId lostNFound = null;
-        if (fix && lostNFoundId != null) {
+        if (fix) {
+            if (lostNFoundId != null) {
-            // do we have a "lost+found" node?
-            try {
-                NodeId tmpid = new NodeId(lostNFoundId);
-                NodePropBundle lfBundle = pm.loadBundle(tmpid);
-                if (lfBundle == null) {
-                    error(lostNFoundId,
+                // do we have a "lost+found" node?
+                try {
+                    NodeId tmpid = new NodeId(lostNFoundId);
+                    NodePropBundle lfBundle = pm.loadBundle(tmpid);
+                    if (lfBundle == null) {
+                        error(lostNFoundId,
-                            "specified 'lost+found' node does not exist");
+                                "Specified 'lost+found' node does not exist: orphaned nodes cannot be fixed");
-                } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle
-                        .getNodeTypeName())) {
-                    error(lostNFoundId,
+                    } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle
+                            .getNodeTypeName())) {
+                        error(lostNFoundId,
-                            "specified 'lost+found' node is not of type nt:unstructured");
+                                "Specified 'lost+found' node is not of type nt:unstructured: orphaned nodes cannot be fixed");
-                } else {
-                    lostNFound = lfBundle.getId();
-                }
-            } catch (Exception ex) {
-                error(lostNFoundId, "finding 'lost+found' folder", ex);
-            }
+                    } else {
+                        lostNFound = lfBundle.getId();
+                    }
+                } catch (Exception ex) {
+                    error(lostNFoundId, "finding 'lost+found' folder", ex);
+                }
+            } else {
+                log.info("No 'lost+found' node specified: orphaned nodes cannot be fixed");
-        }
+            }
+        }
 
         if (uuids == null) {
             try {
-                List<NodeId> allIds = pm.getAllNodeIds(null, NODESATONCE);
+                Map<NodeId, NodeInfo> batch = pm.getAllNodeInfos(null, NODESATONCE);
+                Map<NodeId, NodeInfo> allInfos = batch;
 
-                while (!allIds.isEmpty()) {
+                while (!batch.isEmpty()) {
                     NodeId lastId = null;
 
-                    for (NodeId id : allIds) {
+                    for (Map.Entry<NodeId, NodeInfo> entry : batch.entrySet()) {
+                        NodeId id = entry.getKey();
                         lastId = id;
-                        try {
-                            // parse and check bundle
-                            NodePropBundle bundle = pm.loadBundle(id);
-                            if (bundle == null) {
-                                error(id.toString(), "No bundle found for id '"
-                                        + id + "'");
-                            } else {
-                                checkBundleConsistency(id, bundle, fix,
-                                        modifications, lostNFound, orphaned,
-                                        reports);
 
-                                count++;
+                        count++;
-                                if (count % 1000 == 0 && listener == null) {
-                                    log.info(pm + ": checked " + count
-                                            + " bundles...");
+                        if (count % 1000 == 0) {
+                            log.info(pm + ": loaded " + count + " infos...");
-                                }
+                        }
+
+                        if (!CHECKAFTERLOADING) {
+                            // check immediately
+                            NodeInfo nodeInfo = entry.getValue();
+                            checkBundleConsistency(id, nodeInfo, fix, lostNFound, reports, batch);
-                            }
+                        }
-                        } catch (ItemStateException e) {
-                            // problem already logged (loadBundle called with
-                            // logDetailedErrors=true)
+
-                        }
+                    }
+
+                    batch = pm.getAllNodeInfos(lastId, NODESATONCE);
+
+                    if (CHECKAFTERLOADING) {
+                        allInfos.putAll(batch);
                     }
 
-                    if (!allIds.isEmpty()) {
-                        allIds = pm.getAllNodeIds(lastId, NODESATONCE);
-                    }
+                }
+
+                if (CHECKAFTERLOADING) {
+                    // check info
+                    for (Map.Entry<NodeId, NodeInfo> entry : allInfos.entrySet()) {
+                        checkBundleConsistency(entry.getKey(), entry.getValue(), fix, lostNFound, reports, allInfos);
-                }
+                    }
+                }
+
             } catch (ItemStateException ex) {
                 throw new RepositoryException("getting nodeIds", ex);
             }
@@ -174,8 +186,8 @@
                                     + id + "'");
                         }
                     } else {
-                        checkBundleConsistency(id, bundle, fix, modifications,
-                                lostNFound, orphaned, reports);
+                        NodeInfo nodeInfo = new NodeInfo(bundle);
+                        checkBundleConsistency(id, nodeInfo, fix, lostNFound, reports, Collections.<NodeId, NodeInfo>emptyMap());
 
                         if (recursive) {
                             for (NodePropBundle.ChildNodeEntry entry : bundle
@@ -197,49 +209,6 @@
             }
         }
 
-        // repair collected broken bundles
-        if (fix && !modifications.isEmpty()) {
-            info(null, pm + ": Fixing " + modifications.size()
-                    + " inconsistent bundle(s)...");
-            for (NodePropBundle bundle : modifications) {
-                try {
-                    info(bundle.getId().toString(), pm + ": Fixing bundle '"
-                            + bundle.getId() + "'");
-                    bundle.markOld(); // use UPDATE instead of INSERT
-                    pm.storeBundle(bundle);
-                    pm.evictBundle(bundle.getId());
-                } catch (ItemStateException e) {
-                    error(bundle.getId().toString(), pm
-                            + ": Error storing fixed bundle: " + e);
-                }
-            }
-        }
-
-        if (fix && lostNFoundId != null && !orphaned.isEmpty()) {
-            // do we have things to add to "lost+found"?
-            try {
-                NodePropBundle lfBundle = pm.loadBundle(lostNFound);
-                if (lfBundle == null) {
-                    error(lostNFoundId, "specified 'lost+found' node does not exist");
-                } else if (!NameConstants.NT_UNSTRUCTURED.equals(lfBundle
-                        .getNodeTypeName())) {
-                    error(lostNFoundId, "specified 'lost+found' node is not of type nt:unstructered");
-                } else {
-                    lfBundle.markOld();
-                    for (NodeId orphan : orphaned) {
-                        String nodeName = orphan + "-"
-                                + System.currentTimeMillis();
-                        lfBundle.addChildNodeEntry(NF.create("", nodeName),
-                                orphan);
-                    }
-                    pm.storeBundle(lfBundle);
-                    pm.evictBundle(lfBundle.getId());
-                }
-            } catch (Exception ex) {
-                error(null, "trying orphan adoption", ex);
-            }
-        }
-
         log.info(pm + ": checked " + count + " bundles.");
 
         return count;
@@ -248,24 +217,25 @@
     /**
      * Checks a single bundle for inconsistencies, ie. inexistent child nodes
      * and inexistent parents.
-     * 
+     *
-     * @param id
-     *            node id for the bundle to check
-     * @param bundle
-     *            the bundle to check
-     * @param fix
-     *            if <code>true</code>, repair things that can be repaired
-     * @param modifications
-     *            if <code>fix == true</code>, collect the repaired
-     *            {@linkplain NodePropBundle bundles} here
+     * @param id node id for the bundle to check
+     * @param nodeInfo the node info for the node to check
+     * @param fix if <code>true</code>, repair things that can be repaired
+     * {@linkplain org.apache.jackrabbit.core.persistence.util.NodePropBundle bundles} here
+     * @param infos all the {@link NodeInfo}s loaded in the current batch
      */
-    private void checkBundleConsistency(NodeId id, NodePropBundle bundle,
-            boolean fix, Collection<NodePropBundle> modifications,
-            NodeId lostNFoundId, Set<NodeId> orphaned, Set<ReportItem> reports) {
+    private void checkBundleConsistency(NodeId id, NodeInfo nodeInfo,
+                                        boolean fix, NodeId lostNFoundId,
+                                        Set<ReportItem> reports, Map<NodeId, NodeInfo> infos) {
         // log.info(name + ": checking bundle '" + id + "'");
 
-        // skip all virtual nodes
-        if (isVirtualNode(id)) {
+        if (isRoot(id)) {
+            addReportItem(reports, id, null, "found root node '" + id + "'", ReportItem.CheckerEvent.ROOT);
+        } else if (isVirtualNode(id)) {
+            addReportItem(reports, id, null, "found virtual node '" + id + "'", ReportItem.CheckerEvent.VIRTUAL);
+        }
+        
+        if (shouldSkip(id)) {
             return;
         }
 
@@ -274,132 +244,266 @@
         }
 
         // look at the node's children
-        Collection<NodePropBundle.ChildNodeEntry> missingChildren = new ArrayList<NodePropBundle.ChildNodeEntry>();
-        for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+        Collection<NodePropBundle.ChildNodeEntry> missingChildren = null;
+        Collection<NodePropBundle.ChildNodeEntry> disconnectedChildren = null;
+        NodePropBundle bundle = null;
+        for (NodeId childNodeId : nodeInfo.getChildren()) {
 
             // skip check for system nodes (root, system root, version storage,
             // node types)
-            if (entry.getId().toString().endsWith("babecafebabe")) {
+            if (isVirtualNode(childNodeId)) {
                 continue;
             }
 
             try {
-                // analyze child node bundles
-                NodePropBundle child = pm.loadBundle(entry.getId());
+                // analyze child node info
+                NodeInfo childNodeInfo = infos.get(childNodeId);
+                NodePropBundle childBundle = null;
                 String message = null;
-                if (child == null) {
-                    message = "NodeState '" + id
-                            + "' references inexistent child" + " '"
-                            + entry.getName() + "' with id " + "'"
-                            + entry.getId() + "'";
+
+                // does the child exist?
+                if (childNodeInfo == null) {
+                    // try to load the bundle
+                    childBundle = pm.loadBundle(childNodeId);
+                    if (childBundle == null) {
+                        // the child indeed doesn't exist
+                        // double check whether we still exist and the child entry is still there
+                        if (bundle == null) {
+                            bundle = pm.loadBundle(id);
+                        }
+                        if (bundle != null) {
+                            NodePropBundle.ChildNodeEntry childNodeEntry = null;
+                            for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                                if (entry.getId().equals(childNodeId)) {
+                                    childNodeEntry = entry;
+                                    break;
+                                }
+                            }
+                            if (childNodeEntry != null) {
+                                // indeed we have a missing child
+                                message = "NodeState '" + id + "' references inexistent child" + "'" + childNodeId + "'";
-                    log.error(message);
+                                log.error(message);
-                    missingChildren.add(entry);
+                                addReportItem(reports, id, childNodeId, message, ReportItem.CheckerEvent.MISSING);
+                                if (missingChildren == null) {
+                                    missingChildren = new ArrayList<NodePropBundle.ChildNodeEntry>();
+                                }
+                                missingChildren.add(childNodeEntry);
+                            }
+                        }
-                } else {
+                    } else {
-                    NodeId cp = child.getParentId();
-                    if (cp == null) {
-                        message = "ChildNode has invalid parent id: <null>";
+                        // exists after all
+                        childNodeInfo = new NodeInfo(childBundle);
+                    }
+                }
+                if (childNodeInfo != null) {
+                    // if the child exists does it reference the current node as its parent?
+                    NodeId cp = childNodeInfo.getParentId();
+                    if (cp == null || !cp.equals(id)) {
+                        // the child does not have the current node as its parent
+
+                        // we can distinguish four cases here:
+                        // 1. the child's parent id is of a non-existent node
+                        // 2. the child's parent id is of an existent node and that node has a corresponding child node entry
+                        // 3. the child's parent id is of an existent node but that node does not have a corresponding child node entry
+                        // 4. the child's parent id is null
+                        //
+                        // In 1 and 4 the child is an orphan and will be added to lost+found when it is checked for consistency
+                        // In 2 the current node has an invalid child node entry for the child
+                        // In 3 the child is an abandoned node and will be added again to its parent when it is checked for consistency
+                        // In all cases we should remove the child node entry from the current node
+
+                        // double check whether the child still has a different parent
+                        if (childBundle == null) {
+                            childBundle = pm.loadBundle(childNodeId);
+                        }
+                        if (childBundle != null && !childBundle.getParentId().equals(id)) {
+                            // double check if we still exist
+                            if (bundle == null) {
+                                bundle = pm.loadBundle(id);
+                            }
+                            if (bundle != null) {
+                                // double check if the child node entry is still there
+                                NodePropBundle.ChildNodeEntry childNodeEntry = null;
+                                for (NodePropBundle.ChildNodeEntry entry : bundle.getChildNodeEntries()) {
+                                    if (entry.getId().equals(childNodeId)) {
+                                        // still there
+                                        childNodeEntry = entry;
+                                        break;
+                                    }
+                                }
+                                if (childNodeEntry != null) {
+                                    // indeed the we have a disconnected child
+                                    message = "NodeState '" + id +  "' has child entry '" + childNodeId + "' but child has parent '" + cp + "'";
-                        log.error(message);
+                                    log.error(message);
-                    } else if (!cp.equals(id)) {
-                        message = "ChildNode has invalid parent id: '" + cp
-                                + "' (instead of '" + id + "')";
-                        log.error(message);
+                                    addReportItem(reports, id, childNodeId, message, ReportItem.CheckerEvent.DISCONNECTED);
+                                    if (disconnectedChildren == null) {
+                                        disconnectedChildren = new ArrayList<NodePropBundle.ChildNodeEntry>();
-                    }
+                                    }
+                                    disconnectedChildren.add(childNodeEntry);
-                }
+                                }
-                if (message != null) {
-                    addMessage(reports, id, message);
-                }
+                            }
+                        }
+                    }
+                }
             } catch (ItemStateException e) {
-                // problem already logged (loadBundle called with
-                // logDetailedErrors=true)
-                addMessage(reports, id, e.getMessage());
+                // problem already logged (loadBundle called with logDetailedErrors=true)
+                addReportItem(reports, id, null, e.getMessage(), ReportItem.CheckerEvent.EXCEPTION);
             }
         }
         // remove child node entry (if fixing is enabled)
-        if (fix && !missingChildren.isEmpty()) {
-            for (NodePropBundle.ChildNodeEntry entry : missingChildren) {
-                bundle.getChildNodeEntries().remove(entry);
+        if (fix && (missingChildren != null || disconnectedChildren != null)) {
+            assert bundle != null; // when we have missing children we loaded the bundle during double check
+            if (missingChildren != null) {
+                for (NodePropBundle.ChildNodeEntry childNodeEntry : missingChildren) {
+                    bundle.getChildNodeEntries().remove(childNodeEntry);
-            }
+                }
-            modifications.add(bundle);
-        }
+            }
+            if (disconnectedChildren != null) {
+                for (NodePropBundle.ChildNodeEntry childNodeEntry : disconnectedChildren) {
+                    bundle.getChildNodeEntries().remove(childNodeEntry);
+                }
+            }
+            fixBundle(bundle);
+        }
 
+        if (!isRoot(id)) {
-        // check parent reference
+            // check parent reference
-        NodeId parentId = bundle.getParentId();
+            final NodeId parentId = nodeInfo.getParentId();
-        try {
-            // skip root nodes (that point to itself)
+            try {
+                // skip root nodes (that point to itself)
-            if (parentId != null && !id.toString().endsWith("babecafebabe")) {
-                NodePropBundle parentBundle = pm.loadBundle(parentId);
+                if (!id.toString().endsWith("babecafebabe")) {
+                    NodePropBundle parentBundle = null;
+                    NodeInfo parentInfo = null;
+                    if (parentId != null) {
+                        parentInfo = infos.get(parentId);
+                    }
 
+                    // does the parent exist?
+                    if (parentInfo == null) {
+                        // try to load the bundle
+                        if (parentId != null) {
+                            parentBundle = pm.loadBundle(parentId);
+                        }
-                if (parentBundle == null) {
+                        if (parentBundle == null) {
-                    String message = "NodeState '" + id
-                            + "' references inexistent parent id '" + parentId
-                            + "'";
+                            // indeed the parent doesn't exist
+
+                            // double check whether we still exist and our parent is still the same
+                            if (bundle == null) {
+                                bundle = pm.loadBundle(id);
+                            }
+                            if (bundle != null) {
+                                NodeId parentIdRetry = bundle.getParentId();
+                                if (parentIdRetry == null || parentIdRetry.equals(parentId)) {
+                                    // indeed we have an orphaned node
+                                    String message = "NodeState '" + id + "' references inexistent parent id '" + parentId + "'";
-                    log.error(message);
+                                    log.error(message);
-                    addMessage(reports, id, message);
-                    orphaned.add(id);
-                    if (lostNFoundId != null) {
+                                    addReportItem(reports, id, parentIdRetry, message, ReportItem.CheckerEvent.ORPHAN);
+                                    if (fix && lostNFoundId != null) {
+                                        // add to lost+found
+                                        NodePropBundle lfBundle = pm.loadBundle(lostNFoundId);
+                                        lfBundle.markOld();
+                                        String nodeName = id + "-" + System.currentTimeMillis();
+                                        lfBundle.addChildNodeEntry(NF.create("", nodeName), id);
+                                        pm.storeBundle(lfBundle);
+                                        pm.evictBundle(lostNFoundId);
+
-                        bundle.setParentId(lostNFoundId);
+                                        bundle.setParentId(lostNFoundId);
-                        modifications.add(bundle);
+                                        fixBundle(bundle);
-                    }
+                                    }
+                                }
+                            }
-                } else {
+                        } else {
+                            // parent exists after all
+                            parentInfo = new NodeInfo(parentBundle);
+                        }
+
+                    }
+                    if (parentInfo != null) {
+                        // if the parent exists, does it reference the current node as a child node entry?
-                    boolean found = false;
+                        boolean found = false;
 
-                    for (NodePropBundle.ChildNodeEntry entry : parentBundle
-                            .getChildNodeEntries()) {
+                        for (NodeId childNodeId : parentInfo.getChildren()) {
+                            if (childNodeId.equals(id)){
+                                found = true;
+                                break;
+                            }
+                        }
+
+                        if (!found && parentBundle == null) {
+                            // double check whether parent was not modified in the mean time
+                            parentBundle = pm.loadBundle(parentId);
+                            if (parentBundle != null) {
+                                for (NodePropBundle.ChildNodeEntry entry : parentBundle.getChildNodeEntries()) {
-                        if (entry.getId().equals(id)) {
+                                    if (entry.getId().equals(id)){
+                                        // found after all
-                            found = true;
-                            break;
-                        }
-                    }
+                                        found = true;
+                                        break;
+                                    }
+                                }
+                            }
+                        }
 
-                    if (!found) {
+                        if (!found) {
-                        String message = "NodeState '" + id
-                                + "' is not referenced by its parent node '"
-                                + parentId + "'";
+                            // indeed we have an abandoned node
+                            String message = "NodeState '" + id + "' is not referenced by its parent node '" + parentId + "'";
-                        log.error(message);
+                            log.error(message);
-                        addMessage(reports, id, message);
-
+                            addReportItem(reports, id, parentId, message, ReportItem.CheckerEvent.ABANDONED);
+                            if (fix) {
-                        int l = (int) System.currentTimeMillis();
-                        int r = new Random().nextInt();
-                        int n = l + r;
-                        String nodeName = Integer.toHexString(n);
+                                int l = (int) System.currentTimeMillis();
+                                int r = new Random().nextInt();
+                                int n = l + r;
+                                String nodeName = Integer.toHexString(n);
-                        parentBundle.addChildNodeEntry(
-                                NF.create("{}" + nodeName), id);
-                        log.info("NodeState '" + id
-                                + "' adds itself to its parent node '"
-                                + parentId + "' with a new name '" + nodeName
-                                + "'");
-                        modifications.add(parentBundle);
+                                parentBundle.addChildNodeEntry(NF.create("{}" + nodeName), id);
+                                log.info("NodeState '" + id + "' adds itself to its parent node '" + parentId + "' with a new name '" + nodeName + "'");
+                                fixBundle(parentBundle);
-                    }
-                }
-            }
+                            }
+                        }
+                    }
+                }
-        } catch (ItemStateException e) {
+            } catch (ItemStateException e) {
-            String message = "Error reading node '" + parentId
-                    + "' (parent of '" + id + "'): " + e;
+                String message = "Error reading node '" + parentId + "' (parent of '" + id + "'): " + e;
-            log.error(message);
+                log.error(message);
-            addMessage(reports, id, message);
+                addReportItem(reports, id, null, message, ReportItem.CheckerEvent.EXCEPTION);
-        }
-    }
+            }
+        }
+    }
 
+    private void fixBundle(NodePropBundle bundle) {
+        try {
+            log.info(pm + ": Fixing bundle '" + bundle.getId() + "'");
+            bundle.markOld(); // use UPDATE instead of INSERT
+            pm.storeBundle(bundle);
+            pm.evictBundle(bundle.getId());
+        } catch (ItemStateException e) {
+            log.error(pm + ": Error storing fixed bundle: " + e);
+        }
+    }
+
     /**
-     * @return whether the id is for a virtual node (not needing checking)
+     * @param id  the node id to check
+     * @return whether the is of a root node
      */
-    private boolean isVirtualNode(NodeId id) {
-        String s = id.toString();
-        if ("cafebabe-cafe-babe-cafe-babecafebabe".equals(s)) {
-            // root node isn't virtual
-            return false;
+    private boolean isRoot(final NodeId id) {
+        return "cafebabe-cafe-babe-cafe-babecafebabe".equals(id.toString());
-        }
+    }
-        else {
+
+    /**
+     * @param id the node id to check
+     * @return whether the id is for a virtual node (not needing checking)
+     */
+    protected boolean isVirtualNode(NodeId id) {
-            // all other system nodes are
+        // all other system nodes are
-            return s.endsWith("babecafebabe");
+        return !isRoot(id) && id.toString().endsWith("babecafebabe");
-        }
+    }
+
+    protected boolean shouldSkip(NodeId id) {
+        return isVirtualNode(id);
     }
 
+    private void addReportItem(Set<ReportItem> reports, NodeId id, NodeId objectNodeId, String message, ReportItem.CheckerEvent checkerEvent) {
 
-    private void addMessage(Set<ReportItem> reports, NodeId id, String message) {
-
         if (reports != null || listener != null) {
-            ReportItem ri = new ReportItemImpl(id.toString(), message);
+            ReportItem ri = new ReportItemImpl(id.toString(), objectNodeId == null ? null : objectNodeId.toString(), message, checkerEvent);
 
             if (reports != null) {
                 reports.add(ri);
@@ -435,4 +539,5 @@
             listener.error(id, message);
         }
     }
+
 }
Index: jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java
IDEA additional info:
Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP
<+>UTF-8
===================================================================
--- jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java	(revision 1301887)
+++ jackrabbit-core/src/main/java/org/apache/jackrabbit/core/persistence/bundle/BundleFsPersistenceManager.java	(revision )
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.core.persistence.bundle;
 
+import org.apache.jackrabbit.core.persistence.util.NodeInfo;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.commons.io.IOUtils;
@@ -42,8 +43,12 @@
 import java.sql.SQLException;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
 
+import javax.jcr.RepositoryException;
+
 /**
  * This is a generic persistence manager that stores the {@link NodePropBundle}s
  * in a filesystem.
@@ -494,6 +499,18 @@
             log.error(msg);
             throw new ItemStateException(msg, e);
         }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Map<NodeId, NodeInfo> getAllNodeInfos(NodeId after, int maxCount) throws ItemStateException, RepositoryException {
+        List<NodeId> nodeIds = getAllNodeIds(after, maxCount);
+        Map<NodeId, NodeInfo> result = new LinkedHashMap<NodeId, NodeInfo>(nodeIds.size());
+        for (NodeId nodeId : nodeIds) {
+            result.put(nodeId, new NodeInfo(loadBundle(nodeId)));
+        }
+        return result;
     }
 
     /**
