Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexTest.java	(revision 1463488)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexTest.java	(working copy)
@@ -26,8 +26,10 @@
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
@@ -50,30 +52,36 @@
 
         // Add index definition
         NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("foo")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
-                .setProperty("type", "p2")
+        builder.child("oak:index")
+                .child("foo")
+                .setProperty("jcr:primaryType", "oak:queryIndexDefinition",
+                        Type.NAME).setProperty("type", "p2")
                 .setProperty("propertyNames", "foo");
         NodeState before = builder.getNodeState();
 
         // Add some content and process it through the property index hook
         builder = before.builder();
         builder.child("a").setProperty("foo", "abc");
-        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"), Type.STRINGS);
+        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"),
+                Type.STRINGS);
         // plus lots of dummy content to highlight the benefit of indexing
         for (int i = 0; i < MANY; i++) {
             builder.child("n" + i).setProperty("foo", "xyz");
         }
         NodeState after = builder.getNodeState();
 
-        // Add an index
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         // Query the index
-        Property2IndexLookup lookup = new Property2IndexLookup(builder.getNodeState());
+        Property2IndexLookup lookup = new Property2IndexLookup(indexed);
         assertEquals(ImmutableSet.of("a", "b"), find(lookup, "foo", "abc"));
         assertEquals(ImmutableSet.of("b"), find(lookup, "foo", "def"));
         assertEquals(ImmutableSet.of(), find(lookup, "foo", "ghi"));
@@ -87,11 +95,14 @@
         assertTrue("cost: " + cost, cost >= MANY);
     }
 
-    private static Set<String> find(Property2IndexLookup lookup, String name, String value, Filter filter) {
-        return Sets.newHashSet(lookup.query(filter, name, value == null ? null : PropertyValues.newString(value)));
+    private static Set<String> find(Property2IndexLookup lookup, String name,
+            String value, Filter filter) {
+        return Sets.newHashSet(lookup.query(filter, name, value == null ? null
+                : PropertyValues.newString(value)));
     }
 
-    private static Set<String> find(Property2IndexLookup lookup, String name, String value) {
+    private static Set<String> find(Property2IndexLookup lookup, String name,
+            String value) {
         return find(lookup, name, value, null);
     }
 
@@ -101,16 +112,21 @@
 
         // Add index definition
         NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("fooIndex")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
+        builder.child("oak:index")
+                .child("fooIndex")
+                .setProperty("jcr:primaryType", "oak:queryIndexDefinition",
+                        Type.NAME)
                 .setProperty("type", "p2")
-                .setProperty("propertyNames", Arrays.asList("foo", "extrafoo"), Type.STRINGS);
+                .setProperty("propertyNames", Arrays.asList("foo", "extrafoo"),
+                        Type.STRINGS);
         NodeState before = builder.getNodeState();
 
         // Add some content and process it through the property index hook
         builder = before.builder();
-        builder.child("a").setProperty("foo", "abc").setProperty("extrafoo", "pqr");
-        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"), Type.STRINGS);
+        builder.child("a").setProperty("foo", "abc")
+                .setProperty("extrafoo", "pqr");
+        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"),
+                Type.STRINGS);
         // plus lots of dummy content to highlight the benefit of indexing
         for (int i = 0; i < MANY; i++) {
             builder.child("n" + i).setProperty("foo", "xyz");
@@ -118,19 +134,24 @@
         NodeState after = builder.getNodeState();
 
         // Add an index
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         // Query the index
-        Property2IndexLookup lookup = new Property2IndexLookup(builder.getNodeState());
+        Property2IndexLookup lookup = new Property2IndexLookup(indexed);
         assertEquals(ImmutableSet.of("a", "b"), find(lookup, "foo", "abc"));
         assertEquals(ImmutableSet.of("b"), find(lookup, "foo", "def"));
         assertEquals(ImmutableSet.of(), find(lookup, "foo", "ghi"));
         assertEquals(MANY, find(lookup, "foo", "xyz").size());
         assertEquals(ImmutableSet.of("a"), find(lookup, "extrafoo", "pqr"));
-        
+
         try {
             assertEquals(ImmutableSet.of(), find(lookup, "pqr", "foo"));
             fail();
@@ -178,18 +199,21 @@
         NodeState after = builder.getNodeState();
 
         // Add an index
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
-        NodeState indexedState = builder.getNodeState();
-
         FilterImpl f = new FilterImpl(null, null);
         f.setNodeType("nt:unstructured");
 
         // Query the index
-        Property2IndexLookup lookup = new Property2IndexLookup(indexedState);
+        Property2IndexLookup lookup = new Property2IndexLookup(indexed);
         assertEquals(ImmutableSet.of("a", "b"), find(lookup, "foo", "abc", f));
         assertEquals(ImmutableSet.of("b"), find(lookup, "foo", "def", f));
         assertEquals(ImmutableSet.of(), find(lookup, "foo", "ghi", f));
@@ -239,18 +263,21 @@
         NodeState after = builder.getNodeState();
 
         // Add an index
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
-        NodeState indexedState = builder.getNodeState();
-
         FilterImpl f = new FilterImpl(null, null);
         f.setNodeType("nt:unstructured");
 
         // Query the index
-        Property2IndexLookup lookup = new Property2IndexLookup(indexedState);
+        Property2IndexLookup lookup = new Property2IndexLookup(indexed);
         assertEquals(ImmutableSet.of("a", "b"), find(lookup, "foo", "abc", f));
         assertEquals(ImmutableSet.of("b"), find(lookup, "foo", "def", f));
         assertEquals(ImmutableSet.of(), find(lookup, "foo", "ghi", f));
@@ -285,15 +312,19 @@
                 Type.STRINGS);
         NodeState after = builder.getNodeState();
 
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
         try {
-            p.apply();
+            hook.processCommit(before, after);
             fail("Unique constraint should be respected");
         } catch (CommitFailedException e) {
             // expected
-        } finally {
-            p.close();
         }
     }
 
@@ -303,26 +334,33 @@
 
         // Add index definition
         NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("fooIndex")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
+        builder.child("oak:index")
+                .child("fooIndex")
+                .setProperty("jcr:primaryType", "oak:queryIndexDefinition",
+                        Type.NAME)
                 .setProperty("type", "p2")
                 .setProperty("unique", "true")
-                .setProperty("propertyNames", Arrays.asList("foo"), Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes, Arrays.asList("typeFoo"), Type.STRINGS);
+                .setProperty("propertyNames", Arrays.asList("foo"),
+                        Type.STRINGS)
+                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                        Arrays.asList("typeFoo"), Type.STRINGS);
         NodeState before = builder.getNodeState();
         builder = before.builder();
-        builder.child("a")
-                .setProperty("jcr:primaryType", "typeFoo", Type.NAME)
+        builder.child("a").setProperty("jcr:primaryType", "typeFoo", Type.NAME)
                 .setProperty("foo", "abc");
-        builder.child("b")
-                .setProperty("jcr:primaryType", "typeBar", Type.NAME)
+        builder.child("b").setProperty("jcr:primaryType", "typeBar", Type.NAME)
                 .setProperty("foo", "abc");
         NodeState after = builder.getNodeState();
 
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        hook.processCommit(before, after);
     }
 
     @Test
@@ -331,31 +369,37 @@
 
         // Add index definition
         NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("fooIndex")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
+        builder.child("oak:index")
+                .child("fooIndex")
+                .setProperty("jcr:primaryType", "oak:queryIndexDefinition",
+                        Type.NAME)
                 .setProperty("type", "p2")
                 .setProperty("unique", "true")
-                .setProperty("propertyNames", Arrays.asList("foo"), Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes, Arrays.asList("typeFoo"), Type.STRINGS);
+                .setProperty("propertyNames", Arrays.asList("foo"),
+                        Type.STRINGS)
+                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                        Arrays.asList("typeFoo"), Type.STRINGS);
         NodeState before = builder.getNodeState();
         builder = before.builder();
-        builder.child("a")
-                .setProperty("jcr:primaryType", "typeFoo", Type.NAME)
+        builder.child("a").setProperty("jcr:primaryType", "typeFoo", Type.NAME)
                 .setProperty("foo", "abc");
-        builder.child("b")
-                .setProperty("jcr:primaryType", "typeFoo", Type.NAME)
+        builder.child("b").setProperty("jcr:primaryType", "typeFoo", Type.NAME)
                 .setProperty("foo", "abc");
         NodeState after = builder.getNodeState();
 
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
         try {
-            p.apply();
+            hook.processCommit(before, after);
             fail("Unique constraint should be respected");
         } catch (CommitFailedException e) {
             // expected
-        } finally {
-            p.close();
         }
     }
 
@@ -365,27 +409,34 @@
 
         // Add index definition
         NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("fooIndex")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
+        builder.child("oak:index")
+                .child("fooIndex")
+                .setProperty("jcr:primaryType", "oak:queryIndexDefinition",
+                        Type.NAME)
                 .setProperty("type", "p2")
                 .setProperty("unique", "true")
-                .setProperty("propertyNames", Arrays.asList("foo"), Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes, Arrays.asList("typeFoo"), Type.STRINGS);
-        builder.child("a")
-                .setProperty("jcr:primaryType", "typeFoo", Type.NAME)
+                .setProperty("propertyNames", Arrays.asList("foo"),
+                        Type.STRINGS)
+                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                        Arrays.asList("typeFoo"), Type.STRINGS);
+        builder.child("a").setProperty("jcr:primaryType", "typeFoo", Type.NAME)
                 .setProperty("foo", "abc");
-        builder.child("b")
-                .setProperty("jcr:primaryType", "typeBar", Type.NAME)
+        builder.child("b").setProperty("jcr:primaryType", "typeBar", Type.NAME)
                 .setProperty("foo", "abc");
         NodeState before = builder.getNodeState();
         builder = before.builder();
         builder.removeNode("b");
         NodeState after = builder.getNodeState();
 
-        IndexHook p = new Property2IndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new Property2IndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        hook.processCommit(before, after);
     }
 
 }
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerTest.java	(revision 1463488)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerTest.java	(working copy)
@@ -31,6 +31,9 @@
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.p2.Property2IndexHookProvider;
 import org.apache.jackrabbit.oak.plugins.index.p2.Property2IndexLookup;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -79,8 +82,8 @@
 
         NodeState after = builder.getNodeState();
 
-        IndexHookManager im = new IndexHookManager(
-                new CompositeIndexHookProvider(new Property2IndexHookProvider()));
+        IndexHookManager im = IndexHookManager
+                .of(new Property2IndexHookProvider());
         NodeState indexed = im.processCommit(before, after);
 
         // first check that the index content nodes exist
@@ -98,9 +101,11 @@
         assertEquals(ImmutableSet.of(), find(lookupChild, "foo", "abc"));
 
     }
-    
-    private static Set<String> find(Property2IndexLookup lookup, String name, String value) {
-        return Sets.newHashSet(lookup.query(null, name, PropertyValues.newString(value)));
+
+    private static Set<String> find(Property2IndexLookup lookup, String name,
+            String value) {
+        return Sets.newHashSet(lookup.query(null, name,
+                PropertyValues.newString(value)));
     }
 
     /**
@@ -129,8 +134,8 @@
                         Type.NAME);
         NodeState after = builder.getNodeState();
 
-        IndexHookManager im = new IndexHookManager(
-                new CompositeIndexHookProvider(new Property2IndexHookProvider()));
+        IndexHookManager im = IndexHookManager
+                .of(new Property2IndexHookProvider());
         NodeState indexed = im.processCommit(before, after);
 
         // first check that the index content nodes exist
@@ -170,8 +175,8 @@
                         Type.NAME);
         NodeState after = builder.getNodeState();
 
-        IndexHookManager im = new IndexHookManager(
-                new CompositeIndexHookProvider(new Property2IndexHookProvider()));
+        IndexHookManager im = IndexHookManager
+                .of(new Property2IndexHookProvider());
         NodeState indexed = im.processCommit(before, after);
 
         // first check that the index content nodes exist
@@ -212,8 +217,8 @@
                 .setProperty(REINDEX_PROPERTY_NAME, true);
         NodeState after = builder.getNodeState();
 
-        IndexHookManager im = new IndexHookManager(
-                new CompositeIndexHookProvider(new Property2IndexHookProvider()));
+        IndexHookManager im = IndexHookManager
+                .of(new Property2IndexHookProvider());
         NodeState indexed = im.processCommit(before, after);
 
         // first check that the index content nodes exist
@@ -228,6 +233,51 @@
         assertEquals(ImmutableSet.of("testRoot"), find(lookup, "foo", "abc"));
     }
 
+    @Test
+    public void testIndexDefinitions() throws Exception {
+        NodeState root = EMPTY_NODE;
+
+        NodeBuilder builder = root.builder();
+        // this index is on the current update branch, it should be seen by the
+        // diff
+        builder.child("oak:index")
+                .child("existing")
+                .setProperty("type", "p2")
+                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
+                        Type.NAME);
+
+        NodeState before = builder.getNodeState();
+        // Add index definition
+        builder.child("oak:index")
+                .child("foo")
+                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
+                        Type.NAME);
+        builder.child("test")
+                .child("other")
+                .child("oak:index")
+                .child("index2")
+                .setProperty("type", "p2")
+                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
+                        Type.NAME);
+        NodeState after = builder.getNodeState();
+
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new IndexHookManagerDiff(
+                        new Property2IndexHookProvider(), builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
+
+        // check that the index content nodes exist
+        checkPathExists(indexed, "oak:index", "existing", ":index");
+        checkPathExists(indexed, "test", "other", "oak:index", "index2",
+                ":index");
+    }
+
     private static NodeState checkPathExists(NodeState state, String... verify) {
         NodeState c = state;
         for (String p : verify) {
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java	(revision 1463488)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java	(working copy)
@@ -1,139 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import java.util.Arrays;
-
-import com.google.common.collect.ImmutableSet;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.junit.Test;
-
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static org.junit.Assert.assertEquals;
-
-@Deprecated
-public class PropertyIndexTest {
-
-    private static final int MANY = 100;
-
-    @Test
-    public void testPropertyLookup() throws Exception {
-        NodeState root = EMPTY_NODE;
-
-        // Add index definition
-        NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("foo")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
-                .setProperty("type", "property")
-                .setProperty("propertyNames", "foo");
-        NodeState before = builder.getNodeState();
-
-        // Add some content and process it through the property index hook
-        builder = before.builder();
-        builder.child("a").setProperty("foo", "abc");
-        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"), Type.STRINGS);
-        // plus lots of dummy content to highlight the benefit of indexing
-        for (int i = 0; i < MANY; i++) {
-            builder.child("n" + i).setProperty("foo", "xyz");
-        }
-        NodeState after = builder.getNodeState();
-
-        // First check lookups without an index
-        PropertyIndexLookup lookup = new PropertyIndexLookup(after);
-        long withoutIndex = System.nanoTime();
-        assertEquals(ImmutableSet.of("a", "b"), lookup.find("foo", "abc"));
-        assertEquals(ImmutableSet.of("b"), lookup.find("foo", "def"));
-        assertEquals(ImmutableSet.of(), lookup.find("foo", "ghi"));
-        assertEquals(MANY, lookup.find("foo", "xyz").size());
-        withoutIndex = System.nanoTime() - withoutIndex;
-
-        // ... then see how adding an index affects the code
-        IndexHook p = new PropertyIndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
-
-        lookup = new PropertyIndexLookup(builder.getNodeState());
-        long withIndex = System.nanoTime();
-        assertEquals(ImmutableSet.of("a", "b"), lookup.find("foo", "abc"));
-        assertEquals(ImmutableSet.of("b"), lookup.find("foo", "def"));
-        assertEquals(ImmutableSet.of(), lookup.find("foo", "ghi"));
-        assertEquals(MANY, lookup.find("foo", "xyz").size());
-        withIndex = System.nanoTime() - withIndex;
-
-        // System.out.println("Index performance ratio: " + withoutIndex/withIndex);
-        // assertTrue(withoutIndex > withIndex);
-    }
-
-    @Test
-    public void testCustomConfigPropertyLookup() throws Exception {
-        NodeState root = EMPTY_NODE;
-
-        // Add index definition
-        NodeBuilder builder = root.builder();
-        builder.child("oak:index").child("fooIndex")
-                .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
-                .setProperty("type", "property")
-                .setProperty("propertyNames", Arrays.asList("foo", "extrafoo"), Type.STRINGS);
-        NodeState before = builder.getNodeState();
-
-        // Add some content and process it through the property index hook
-        builder = before.builder();
-        builder.child("a").setProperty("foo", "abc").setProperty("extrafoo", "pqr");
-        builder.child("b").setProperty("foo", Arrays.asList("abc", "def"), Type.STRINGS);
-        // plus lots of dummy content to highlight the benefit of indexing
-        for (int i = 0; i < MANY; i++) {
-            builder.child("n" + i).setProperty("foo", "xyz");
-        }
-        NodeState after = builder.getNodeState();
-
-        // First check lookups without an index
-        PropertyIndexLookup lookup = new PropertyIndexLookup(after);
-        long withoutIndex = System.nanoTime();
-        assertEquals(ImmutableSet.of("a", "b"), lookup.find("foo", "abc"));
-        assertEquals(ImmutableSet.of("b"), lookup.find("foo", "def"));
-        assertEquals(ImmutableSet.of(), lookup.find("foo", "ghi"));
-        assertEquals(MANY, lookup.find("foo", "xyz").size());
-        assertEquals(ImmutableSet.of("a"), lookup.find("extrafoo", "pqr"));
-        assertEquals(ImmutableSet.of(), lookup.find("pqr", "foo"));
-        withoutIndex = System.nanoTime() - withoutIndex;
-
-        // ... then see how adding an index affects the code
-        IndexHook p = new PropertyIndexDiff(builder);
-        after.compareAgainstBaseState(before, p);
-        p.apply();
-        p.close();
-
-        lookup = new PropertyIndexLookup(builder.getNodeState());
-        long withIndex = System.nanoTime();
-        assertEquals(ImmutableSet.of("a", "b"), lookup.find("foo", "abc"));
-        assertEquals(ImmutableSet.of("b"), lookup.find("foo", "def"));
-        assertEquals(ImmutableSet.of(), lookup.find("foo", "ghi"));
-        assertEquals(MANY, lookup.find("foo", "xyz").size());
-        assertEquals(ImmutableSet.of("a"), lookup.find("extrafoo", "pqr"));
-        assertEquals(ImmutableSet.of(), lookup.find("pqr", "foo"));
-        withIndex = System.nanoTime() - withIndex;
-
-        // System.out.println("Index performance ratio: " + withoutIndex/withIndex);
-        // assertTrue(withoutIndex > withIndex);
-    }
-
-
-}
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java	(revision 1463488)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexQueryTest.java	(working copy)
@@ -1,70 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-
-import org.apache.jackrabbit.JcrConstants;
-import org.apache.jackrabbit.oak.Oak;
-import org.apache.jackrabbit.oak.api.ContentRepository;
-import org.apache.jackrabbit.oak.api.Tree;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
-import org.apache.jackrabbit.oak.query.AbstractQueryTest;
-import org.junit.Ignore;
-import org.junit.Test;
-
-/**
- * Tests the query engine using the default index implementation: the
- * {@link PropertyIndexProvider}
- */
-@Deprecated
-public class PropertyIndexQueryTest extends AbstractQueryTest {
-
-    @Override
-    protected ContentRepository createRepository() {
-        return new Oak()
-            .with(new InitialContent())
-            .with(new PropertyIndexProvider())
-            .with(new PropertyIndexHookProvider())
-            .createContentRepository();
-    }
-
-    @Override
-    protected void createTestIndexNode() throws Exception {
-        Tree index = root.getTree("/");
-        Tree indexDef = index.addChild(INDEX_DEFINITIONS_NAME).addChild(
-                TEST_INDEX_NAME);
-        indexDef.setProperty(JcrConstants.JCR_PRIMARYTYPE,
-                INDEX_DEFINITIONS_NODE_TYPE, Type.NAME);
-        indexDef.setProperty(TYPE_PROPERTY_NAME, PropertyIndex.TYPE);
-        indexDef.setProperty(REINDEX_PROPERTY_NAME, true);
-        indexDef.setProperty("propertyNames", "jcr:uuid");
-        root.commit();
-    }
-
-    @Test
-    @Ignore
-    // not implemented, see OAK-515
-    public void sql2Index() throws Exception {
-        test("sql2_index.txt");
-    }
-
-}
\ No newline at end of file
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiffTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiffTest.java	(revision 1463488)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiffTest.java	(working copy)
@@ -1,120 +0,0 @@
-/*
- * 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.oak.plugins.index;
-
-import static com.google.common.collect.Sets.difference;
-import static com.google.common.collect.Sets.newHashSet;
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.p2.Property2IndexHookProvider;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
-import org.junit.Test;
-
-public class IndexHookManagerDiffTest {
-
-    @Test
-    public void testIndexDefinitions() throws Exception {
-        NodeState root = EMPTY_NODE;
-
-        NodeBuilder builder = root.builder();
-        // this index is on the current update branch, it should be seen by the
-        // diff
-        builder.child("oak:index")
-                .child("existing")
-                .setProperty("type", "p2")
-                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
-                        Type.NAME);
-        // this index is NOT the current update branch, it should NOT be seen by
-        // the diff
-        builder.child("newchild")
-                .child("other")
-                .child("oak:index")
-                .child("existing2")
-                .setProperty("type", "p2")
-                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
-                        Type.NAME);
-
-        NodeState before = builder.getNodeState();
-        // Add index definition
-        builder.child("oak:index")
-                .child("foo")
-                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
-                        Type.NAME);
-        builder.child("test")
-                .child("other")
-                .child("oak:index")
-                .child("index2")
-                .setProperty("type", "p2")
-                .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE,
-                        Type.NAME);
-        NodeState after = builder.getNodeState();
-
-        IndexHookProvider provider = new CompositeIndexHookProvider(
-                new Property2IndexHookProvider());
-
-        // <type, <path, indexhook>>
-        Map<String, Map<String, List<IndexHook>>> updates = new HashMap<String, Map<String, List<IndexHook>>>();
-        NodeStateDiff diff = new IndexHookManagerDiff(provider, builder,
-                updates);
-        after.compareAgainstBaseState(before, diff);
-
-        for (String type : updates.keySet()) {
-            for (List<IndexHook> hooks : updates.get(type).values()) {
-                for (IndexHook hook : hooks) {
-                    hook.apply();
-                }
-            }
-        }
-
-        Set<String> expected = newHashSet("/", "/test/other");
-        Set<String> found = updates.remove("p2").keySet();
-        assertTrue("Expecting " + expected + " got " + found,
-                difference(found, expected).isEmpty());
-        assertTrue(updates.isEmpty());
-
-        NodeState indexed = builder.getNodeState();
-
-        // check that the index content nodes exist
-        checkPathExists(indexed, "oak:index", "existing", ":index");
-        checkPathExists(indexed, "test", "other", "oak:index", "index2",
-                ":index");
-        NodeState ignoredIndex = checkPathExists(indexed, "newchild", "other",
-                "oak:index", "existing2");
-        assertFalse(ignoredIndex.hasChildNode(":index"));
-    }
-
-    private static NodeState checkPathExists(NodeState state, String... verify) {
-        NodeState c = state;
-        for (String p : verify) {
-            assertTrue(c.hasChildNode(p));
-            c = c.getChildNode(p);
-        }
-        return c;
-    }
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyIndexConstants.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyIndexConstants.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/old/PropertyIndexConstants.java	(working copy)
@@ -17,7 +17,6 @@
 package org.apache.jackrabbit.oak.plugins.index.old;
 
 import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
-import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
 
 public interface PropertyIndexConstants {
 
@@ -27,7 +26,7 @@
      * The root node of the index definition (configuration) nodes.
      */
     // TODO OAK-178 discuss where to store index config data
-    String INDEX_CONFIG_PATH = "/" + IndexUtils.INDEX_DEFINITIONS_NAME + "/indexes";
+    String INDEX_CONFIG_PATH = "/oak:index/indexes";
     // "/jcr:system/indexes";
 
     /**
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexUpdate.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexUpdate.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexUpdate.java	(working copy)
@@ -102,8 +102,10 @@
     /**
      * A property value was added at the given path.
      * 
-     * @param path the path
-     * @param value the value
+     * @param path
+     *            the path
+     * @param value
+     *            the value
      */
     public void insert(String path, PropertyState value) {
         Preconditions.checkArgument(path.startsWith(this.path));
@@ -113,8 +115,10 @@
     /**
      * A property value was removed at the given path.
      * 
-     * @param path the path
-     * @param value the value
+     * @param path
+     *            the path
+     * @param value
+     *            the value
      */
     public void remove(String path, PropertyState value) {
         Preconditions.checkArgument(path.startsWith(this.path));
@@ -141,9 +145,9 @@
     }
 
     boolean getAndResetReindexFlag() {
-        boolean reindex = node.getProperty(REINDEX_PROPERTY_NAME) != null
-                && node.getProperty(REINDEX_PROPERTY_NAME).getValue(
-                        Type.BOOLEAN);
+        PropertyState reindexPS = node.getProperty(REINDEX_PROPERTY_NAME);
+        boolean reindex = reindexPS == null || reindexPS != null
+                && reindexPS.getValue(Type.BOOLEAN);
         node.setProperty(REINDEX_PROPERTY_NAME, false);
         return reindex;
     }
@@ -151,7 +155,8 @@
     /**
      * Try to apply the changes to the index content (to the ":index" node.
      * 
-     * @throws CommitFailedException if a unique index was violated
+     * @throws CommitFailedException
+     *             if a unique index was violated
      */
     public void apply() throws CommitFailedException {
         boolean unique = node.getProperty("unique") != null
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexDiff.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexDiff.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexDiff.java	(working copy)
@@ -16,6 +16,16 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.p2;
 
+import static com.google.common.collect.Lists.newArrayList;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.p2.Property2Index.TYPE;
+import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collections;
@@ -23,25 +33,20 @@
 import java.util.List;
 import java.util.Map;
 
-import com.google.common.collect.ImmutableList;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.IndexHook;
 import org.apache.jackrabbit.oak.plugins.index.p2.strategy.ContentMirrorStoreStrategy;
 import org.apache.jackrabbit.oak.plugins.index.p2.strategy.IndexStoreStrategy;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
 
-import static com.google.common.collect.Lists.newArrayList;
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.p2.Property2Index.TYPE;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import com.google.common.collect.ImmutableList;
 
 /**
  * {@link IndexHook} implementation that is responsible for keeping the
@@ -49,17 +54,16 @@
  * <p/>
  * There is a tree of PropertyIndexDiff objects, each object represents the
  * changes at a given node.
- *
+ * 
  * @see Property2Index
  * @see Property2IndexLookup
  */
-class Property2IndexDiff implements IndexHook {
+class Property2IndexDiff implements IndexHook, Closeable {
 
     protected static String propertyNames = "propertyNames";
 
     protected static String declaringNodeTypes = "declaringNodeTypes";
 
-
     private final IndexStoreStrategy store = new ContentMirrorStoreStrategy();
 
     /**
@@ -83,41 +87,36 @@
     private String path;
 
     /**
-     * The map of known indexes.
-     * Key: the property name. Value: the list of indexes (it is possible to
-     * have multiple indexes for the same property name).
+     * The map of known indexes. Key: the property name. Value: the list of
+     * indexes (it is possible to have multiple indexes for the same property
+     * name).
      */
     private final Map<String, List<Property2IndexUpdate>> indexMap;
 
+    /**
+     * the root editor in charge of applying the updates
+     */
+    private final boolean isRoot;
+
     public Property2IndexDiff(NodeBuilder root) {
         this(null, root, null, "/",
-                new HashMap<String, List<Property2IndexUpdate>>());
+                new HashMap<String, List<Property2IndexUpdate>>(), true);
     }
 
     private Property2IndexDiff(Property2IndexDiff parent, String nodeName) {
-        this(parent, getChildNode(parent.node, nodeName),
-                nodeName, null, parent.indexMap);
+        this(parent, getChildNode(parent.node, nodeName), nodeName, null,
+                parent.indexMap, false);
     }
 
-    private Property2IndexDiff(
-            Property2IndexDiff parent,
-            NodeBuilder node, String nodeName, String path,
-            Map<String, List<Property2IndexUpdate>> indexMap) {
+    private Property2IndexDiff(Property2IndexDiff parent, NodeBuilder node,
+            String nodeName, String path,
+            Map<String, List<Property2IndexUpdate>> indexMap, boolean isRoot) {
         this.parent = parent;
         this.node = node;
         this.nodeName = nodeName;
         this.path = path;
         this.indexMap = indexMap;
-
-        if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
-            NodeBuilder index = node.child(INDEX_DEFINITIONS_NAME);
-            for (String indexName : index.getChildNodeNames()) {
-                NodeBuilder child = index.child(indexName);
-                if (isIndexNode(child)) {
-                    addIndexes(child, indexName);
-                }
-            }
-        }
+        this.isRoot = isRoot;
     }
 
     private static NodeBuilder getChildNode(NodeBuilder node, String name) {
@@ -128,7 +127,6 @@
         }
     }
 
-    @Override
     public String getPath() {
         // build the path lazily
         if (path == null) {
@@ -139,8 +137,9 @@
 
     /**
      * Get all the indexes for the given property name.
-     *
-     * @param propertyName the property name
+     * 
+     * @param propertyName
+     *            the property name
      * @return the indexes
      */
     private Iterable<Property2IndexUpdate> getIndexes(String propertyName) {
@@ -171,10 +170,13 @@
     }
 
     /**
-     * Add the index definitions to the in-memory set of known index definitions.
+     * Add the index definitions to the in-memory set of known index
+     * definitions.
      * 
-     * @param builder the node builder that contains the index definition
-     * @param indexName the name of the index
+     * @param builder
+     *            the node builder that contains the index definition
+     * @param indexName
+     *            the name of the index
      */
     private void addIndexes(NodeBuilder builder, String indexName) {
         List<String> typeNames = ImmutableList.of();
@@ -203,7 +205,8 @@
                 }
             }
             if (!exists) {
-                list.add(new Property2IndexUpdate(getPath(), builder, store, typeNames));
+                list.add(new Property2IndexUpdate(getPath(), builder, store,
+                        typeNames));
             }
         }
     }
@@ -221,9 +224,34 @@
         return isIndexType;
     }
 
-    //-----------------------------------------------------< NodeStateDiff >--
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
+            NodeBuilder index = node.child(INDEX_DEFINITIONS_NAME);
+            for (String indexName : index.getChildNodeNames()) {
+                NodeBuilder child = index.child(indexName);
+                if (isIndexNode(child)) {
+                    addIndexes(child, indexName);
+                }
+            }
+        }
+    }
 
     @Override
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (!isRoot) {
+            return;
+        }
+        for (List<Property2IndexUpdate> updateList : indexMap.values()) {
+            for (Property2IndexUpdate update : updateList) {
+                update.apply();
+            }
+        }
+    }
+
+    @Override
     public void propertyAdded(PropertyState after) {
         for (Property2IndexUpdate update : getIndexes(after.getName())) {
             update.insert(getPath(), after);
@@ -246,35 +274,27 @@
     }
 
     @Override
-    public void childNodeAdded(String name, NodeState after) {
-        childNodeChanged(name, EMPTY_NODE, after);
+    public Editor childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
+        return childNodeChanged(name, EMPTY_NODE, after);
     }
 
     @Override
-    public void childNodeChanged(
-            String name, NodeState before, NodeState after) {
-        if (!NodeStateUtils.isHidden(name)) {
-            after.compareAgainstBaseState(before, child(name));
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) throws CommitFailedException {
+        if (NodeStateUtils.isHidden(name)) {
+            return null;
         }
+        return new Property2IndexDiff(this, name);
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
-        childNodeChanged(name, before, EMPTY_NODE);
+    public Editor childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        return childNodeChanged(name, before, EMPTY_NODE);
     }
 
-    // -----------------------------------------------------< IndexHook >--
-
     @Override
-    public void apply() throws CommitFailedException {
-        for (List<Property2IndexUpdate> updateList : indexMap.values()) {
-            for (Property2IndexUpdate update : updateList) {
-                update.apply();
-            }
-        }
-    }
-
-    @Override
     public void reindex(NodeBuilder state) throws CommitFailedException {
         boolean reindex = false;
         for (List<Property2IndexUpdate> updateList : indexMap.values()) {
@@ -285,16 +305,19 @@
             }
         }
         if (reindex) {
-            state.getNodeState().compareAgainstBaseState(
-                    EMPTY_NODE,
-                    new Property2IndexDiff(null, state, null, "/", indexMap));
+            EditorProvider provider = new EditorProvider() {
+                @Override
+                public Editor getRootEditor(NodeState before, NodeState after,
+                        NodeBuilder builder) {
+                    return new Property2IndexDiff(node);
+                }
+            };
+            EditorHook eh = new EditorHook(provider);
+            eh.processCommit(EMPTY_NODE, state.getNodeState());
         }
     }
 
-    @Override
-    public IndexHook child(String name) {
-        return new Property2IndexDiff(this, name);
-    }
+    // -----------------------------------------------------< Closeable >--
 
     @Override
     public void close() throws IOException {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java	(working copy)
@@ -1,153 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import java.io.UnsupportedEncodingException;
-import java.net.URLEncoder;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Set;
-
-import org.apache.jackrabbit.oak.api.PropertyValue;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.spi.query.Cursor;
-import org.apache.jackrabbit.oak.spi.query.Cursors;
-import org.apache.jackrabbit.oak.spi.query.Filter;
-import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction;
-import org.apache.jackrabbit.oak.spi.query.QueryIndex;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Sets;
-
-/**
- * Provides a QueryIndex that does lookups against a property index
- * 
- * <p>
- * To define a property index on a subtree you have to add an <code>oak:index</code> node.
- * 
- * Under it follows the index definition node that:
- * <ul>
- * <li>must be of type <code>oak:queryIndexDefinition</code></li>
- * <li>must have the <code>type</code> property set to <b><code>property</code></b></li>
- * <li>contains the <code>propertyNames</code> property that indicates what property will be stored in the index</li>
- * </ul>
- * </p>
- * <p>
- * Optionally you can specify the uniqueness constraint on a property index by
- * setting the <code>unique</code> flag to <code>true</code>.
- * </p>
- * 
- * <p>
- * Note: <code>propertyNames</code> can be a list of properties, and it is optional.in case it is missing, the node name will be used as a property name reference value
- * </p>
- * 
- * <p>
- * Note: <code>reindex</code> is a property that when set to <code>true</code>, triggers a full content reindex.
- * </p>
- * 
- * <pre>
- * <code>
- * {
- *     NodeBuilder index = root.child("oak:index");
- *     index.child("uuid")
- *         .setProperty("jcr:primaryType", "oak:queryIndexDefinition", Type.NAME)
- *         .setProperty("type", "property")
- *         .setProperty("propertyNames", "jcr:uuid")
- *         .setProperty("unique", true)
- *         .setProperty("reindex", true);
- * }
- * </code>
- * </pre>
- * 
- * @see QueryIndex
- * @see PropertyIndexLookup
- */
-@Deprecated
-class PropertyIndex implements QueryIndex {
-
-    public static final String TYPE = "property";
-
-    private static final int MAX_STRING_LENGTH = 100; // TODO: configurable
-
-    static List<String> encode(PropertyValue value) {
-        List<String> values = new ArrayList<String>();
-
-        for (String v : value.getValue(Type.STRINGS)) {
-            try {
-                if (v.length() > MAX_STRING_LENGTH) {
-                    v = v.substring(0, MAX_STRING_LENGTH);
-                }
-                values.add(URLEncoder.encode(v, Charsets.UTF_8.name()));
-            } catch (UnsupportedEncodingException e) {
-                throw new IllegalStateException("UTF-8 is unsupported", e);
-            }
-        }
-        return values;
-    }
-
-
-    //--------------------------------------------------------< QueryIndex >--
-
-    @Override
-    public String getIndexName() {
-        return "oak:index";
-    }
-
-    @Override
-    public double getCost(Filter filter, NodeState root) {
-        PropertyIndexLookup lookup = new PropertyIndexLookup(root);
-        for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
-            if (pr.firstIncluding && pr.lastIncluding
-                    && pr.first.equals(pr.last) // TODO: range queries
-                    && lookup.isIndexed(pr.propertyName, "/")) { // TODO: path
-                return lookup.getCost(pr.propertyName, pr.first);
-            }
-        }
-        // not an appropriate index
-        return Double.POSITIVE_INFINITY;
-    }
-
-    @Override
-    public Cursor query(Filter filter, NodeState root) {
-        Set<String> paths = null;
-
-        PropertyIndexLookup lookup = new PropertyIndexLookup(root);
-        for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
-            if (pr.firstIncluding && pr.lastIncluding
-                    && pr.first.equals(pr.last) // TODO: range queries
-                    && lookup.isIndexed(pr.propertyName, "/")) { // TODO: path
-                Set<String> set = lookup.find(pr.propertyName, pr.first);
-                if (paths == null) {
-                    paths = Sets.newHashSet(set);
-                } else {
-                    paths.retainAll(set);
-                }
-            }
-        }
-
-        if (paths == null) {
-            throw new IllegalStateException("Property index is used even when no index is available for filter " + filter);
-        }
-        return Cursors.newPathCursor(paths);
-    }
-
-    @Override
-    public String getPlan(Filter filter, NodeState root) {
-        return "oak:index"; // TODO: better plans
-    }
-}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexUpdate.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexUpdate.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexUpdate.java	(working copy)
@@ -1,179 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
-
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.jcr.PropertyType;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.memory.MemoryPropertyBuilder;
-import org.apache.jackrabbit.oak.spi.query.PropertyValues;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.PropertyBuilder;
-
-/**
- * Takes care of applying the updates to the index content.
- * <p>
- * The changes are temporarily added to an in-memory structure, and then applied
- * to the node.
- */
-@Deprecated
-class PropertyIndexUpdate {
-
-    /**
-     * The path of the index definition (where the index data is stored).
-     */
-    private final String path;
-
-    /**
-     * The node where the index definition is stored.
-     */
-    private final NodeBuilder node;
-
-    /**
-     * The set of added values / paths. The key of the map is the property value
-     * (encoded as a string), the value of the map is a set of paths that where
-     * added.
-     */
-    private final Map<String, Set<String>> insert;
-
-    /**
-     * The set of removed values / paths. The key of the map is the property
-     * value (encoded as a string), the value of the map is a set of paths that
-     * were removed.
-     */
-    private final Map<String, Set<String>> remove;
-
-    public PropertyIndexUpdate(String path, NodeBuilder node) {
-        this.path = path;
-        this.node = node;
-        this.insert = Maps.newHashMap();
-        this.remove = Maps.newHashMap();
-    }
-
-    String getPath() {
-        return path;
-    }
-
-    /**
-     * A property value was added at the given path.
-     * 
-     * @param path the path
-     * @param value the value
-     */
-    public void insert(String path, PropertyState value) {
-        Preconditions.checkArgument(path.startsWith(this.path));
-        putValues(insert, path.substring(this.path.length()), value);
-    }
-
-    /**
-     * A property value was removed at the given path.
-     * 
-     * @param path the path
-     * @param value the value
-     */
-    public void remove(String path, PropertyState value) {
-        Preconditions.checkArgument(path.startsWith(this.path));
-        putValues(remove, path.substring(this.path.length()), value);
-    }
-
-    private static void putValues(Map<String, Set<String>> map, String path,
-            PropertyState value) {
-        if (value.getType().tag() != PropertyType.BINARY) {
-            List<String> keys = PropertyIndex.encode(PropertyValues.create(value));
-            for (String key : keys) {
-                Set<String> paths = map.get(key);
-                if (paths == null) {
-                    paths = Sets.newHashSet();
-                    map.put(key, paths);
-                }
-                paths.add(path);
-            }
-        }
-    }
-
-    boolean getAndResetReindexFlag() {
-        boolean reindex = node.getProperty(REINDEX_PROPERTY_NAME) != null
-                && node.getProperty(REINDEX_PROPERTY_NAME).getValue(
-                        Type.BOOLEAN);
-        node.setProperty(REINDEX_PROPERTY_NAME, false);
-        return reindex;
-    }
-
-    /**
-     * Try to apply the changes to the index content (to the ":index" node.
-     * 
-     * @throws CommitFailedException if a unique index was violated
-     */
-    public void apply() throws CommitFailedException {
-        boolean unique = node.getProperty("unique") != null
-                && node.getProperty("unique").getValue(Type.BOOLEAN);
-        NodeBuilder index = node.child(":index");
-
-        for (Map.Entry<String, Set<String>> entry : remove.entrySet()) {
-            String encoded = entry.getKey();
-            Set<String> paths = entry.getValue();
-            PropertyState property = index.getProperty(encoded);
-            if (property != null) {
-                PropertyBuilder<String> builder = MemoryPropertyBuilder.array(Type.STRING, encoded);
-                for (String value : property.getValue(Type.STRINGS)) {
-                    if (!paths.contains(value)) {
-                        builder.addValue(value);
-                    }
-                }
-                if (builder.isEmpty()) {
-                    index.removeProperty(encoded);
-                } else {
-                    index.setProperty(builder.getPropertyState());
-                }
-            }
-        }
-
-        for (Map.Entry<String, Set<String>> entry : insert.entrySet()) {
-            String encoded = entry.getKey();
-            Set<String> paths = entry.getValue();
-            PropertyState property = index.getProperty(encoded);
-            PropertyBuilder<String> builder = MemoryPropertyBuilder.array(Type.STRING)
-                    .setName(encoded)
-                    .assignFrom(property);
-            for (String path : paths) {
-                if (!builder.hasValue(path)) {
-                    builder.addValue(path);
-                }
-            }
-            if (builder.isEmpty()) {
-                index.removeProperty(encoded);
-            } else if (unique && builder.count() > 1) {
-                throw new CommitFailedException(
-                        "Uniqueness constraint violated");
-            } else {
-                index.setProperty(builder.getPropertyState());
-            }
-        }
-    }
-
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDiff.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDiff.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexDiff.java	(working copy)
@@ -1,260 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.TYPE;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
-
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Lists;
-import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
-
-/**
- * {@link IndexHook} implementation that is responsible for keeping the
- * {@link PropertyIndex} up to date.
- * <p>
- * There is a tree of PropertyIndexDiff objects, each object represents the
- * changes at a given node.
- * 
- * @see PropertyIndex
- * @see PropertyIndexLookup
- * 
- */
-@Deprecated
-class PropertyIndexDiff implements IndexHook {
-
-    /**
-     * The parent (null if this is the root node).
-     */
-    private final PropertyIndexDiff parent;
-
-    /**
-     * The node (never null).
-     */
-    private final NodeBuilder node;
-
-    /**
-     * The node name (the path element). Null for the root node.
-     */
-    private final String name;
-
-    /**
-     * The path of the changed node (built lazily).
-     */
-    private String path;
-
-    /**
-     * Key: the property name. Value: the list of indexes (it is possible to
-     * have multiple indexes for the same property name).
-     */
-    private final Map<String, List<PropertyIndexUpdate>> updates;
-
-    private PropertyIndexDiff(
-            PropertyIndexDiff parent,
-            NodeBuilder node, String name, String path,
-            Map<String, List<PropertyIndexUpdate>> updates) {
-        this.parent = parent;
-        this.node = node;
-        this.name = name;
-        this.path = path;
-        this.updates = updates;
-
-        if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
-            NodeBuilder index = node.child(INDEX_DEFINITIONS_NAME);
-            for (String indexName : index.getChildNodeNames()) {
-                NodeBuilder child = index.child(indexName);
-                if (isIndexNode(child)) {
-                    update(child, indexName);
-                }
-            }
-        }
-    }
-
-    private PropertyIndexDiff(PropertyIndexDiff parent, String name) {
-        this(parent, getChildNode(parent.node, name),
-                name, null, parent.updates);
-    }
-
-    public PropertyIndexDiff(NodeBuilder root) {
-        this(null, root, null, "/",
-                new HashMap<String, List<PropertyIndexUpdate>>());
-    }
-
-    private static NodeBuilder getChildNode(NodeBuilder node, String name) {
-        if (node != null && node.hasChildNode(name)) {
-            return node.child(name);
-        } else {
-            return null;
-        }
-    }
-
-    @Override
-    public String getPath() {
-        // build the path lazily
-        if (path == null) {
-            path = concat(parent.getPath(), name);
-        }
-        return path;
-    }
-
-    /**
-     * Get all the indexes for the given property name.
-     * 
-     * @param name the property name
-     * @return the indexes
-     */
-    private Iterable<PropertyIndexUpdate> getIndexes(String name) {
-        List<PropertyIndexUpdate> indexes = updates.get(name);
-        if (indexes != null) {
-            return indexes;
-        } else {
-            return ImmutableList.of();
-        }
-    }
-
-    private void update(NodeBuilder builder, String indexName) {
-        PropertyState ps = builder.getProperty("propertyNames");
-        Iterable<String> propertyNames = ps != null ? ps.getValue(Type.STRINGS)
-                : ImmutableList.of(indexName);
-        for (String pname : propertyNames) {
-            List<PropertyIndexUpdate> list = this.updates.get(pname);
-            if (list == null) {
-                list = Lists.newArrayList();
-                this.updates.put(pname, list);
-            }
-            boolean exists = false;
-            for (PropertyIndexUpdate piu : list) {
-                if (piu.getPath().equals(getPath())) {
-                    exists = true;
-                    break;
-                }
-            }
-            if (!exists) {
-                list.add(new PropertyIndexUpdate(getPath(), builder));
-            }
-        }
-    }
-
-    private static boolean isIndexNode(NodeBuilder node) {
-        PropertyState ps = node.getProperty(JCR_PRIMARYTYPE);
-        boolean isNodeType = ps != null && !ps.isArray()
-                && ps.getValue(Type.STRING).equals(INDEX_DEFINITIONS_NODE_TYPE);
-        if (!isNodeType) {
-            return false;
-        }
-        PropertyState type = node.getProperty(TYPE_PROPERTY_NAME);
-        boolean isIndexType = type != null && !type.isArray()
-                && type.getValue(Type.STRING).equals(TYPE);
-        return isIndexType;
-    }
-
-    //-----------------------------------------------------< NodeStateDiff >--
-
-    @Override
-    public void propertyAdded(PropertyState after) {
-        for (PropertyIndexUpdate update : getIndexes(after.getName())) {
-            update.insert(getPath(), after);
-        }
-    }
-
-    @Override
-    public void propertyChanged(PropertyState before, PropertyState after) {
-        for (PropertyIndexUpdate update : getIndexes(after.getName())) {
-            update.remove(getPath(), before);
-            update.insert(getPath(), after);
-        }
-    }
-
-    @Override
-    public void propertyDeleted(PropertyState before) {
-        for (PropertyIndexUpdate update : getIndexes(before.getName())) {
-            update.remove(getPath(), before);
-        }
-    }
-
-    @Override
-    public void childNodeAdded(String name, NodeState after) {
-        childNodeChanged(name, EMPTY_NODE, after);
-    }
-
-    @Override
-    public void childNodeChanged(
-            String name, NodeState before, NodeState after) {
-        if (!NodeStateUtils.isHidden(name)) {
-            after.compareAgainstBaseState(before, child(name));
-        }
-    }
-
-    @Override
-    public void childNodeDeleted(String name, NodeState before) {
-        childNodeChanged(name, before, EMPTY_NODE);
-    }
-
-    // -----------------------------------------------------< IndexHook >--
-
-    @Override
-    public void apply() throws CommitFailedException {
-        for (List<PropertyIndexUpdate> updateList : updates.values()) {
-            for (PropertyIndexUpdate update : updateList) {
-                update.apply();
-            }
-        }
-    }
-
-    @Override
-    public void reindex(NodeBuilder state) throws CommitFailedException {
-        boolean reindex = false;
-        for (List<PropertyIndexUpdate> updateList : updates.values()) {
-            for (PropertyIndexUpdate update : updateList) {
-                if (update.getAndResetReindexFlag()) {
-                    reindex = true;
-                }
-            }
-        }
-        if (reindex) {
-            state.getNodeState().compareAgainstBaseState(
-                    EMPTY_NODE,
-                    new PropertyIndexDiff(null, state, null, "/", updates));
-        }
-    }
-
-    @Override
-    public IndexHook child(String name) {
-        return new PropertyIndexDiff(this, name);
-    }
-
-    @Override
-    public void close() throws IOException {
-        updates.clear();
-    }
-}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java	(working copy)
@@ -1,212 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.api.PropertyValue;
-import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
-import org.apache.jackrabbit.oak.spi.query.PropertyValues;
-import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-
-import com.google.common.collect.Sets;
-
-/**
- * Is responsible for querying the property index content.
- * 
- * <p>
- * This class can be used directly on a subtree where there is an index defined
- * by supplying a {@link NodeState} root.
- * </p>
- * 
- * <pre>
- * <code>
- * {
- *     NodeState state = ... // get a node state
- *     PropertyIndexLookup lookup = new PropertyIndexLookup(state);
- *     Set<String> hits = lookup.find("foo", PropertyValues.newString("xyz"));
- * }
- * </code>
- * </pre>
- * 
- * @deprecated please use the {@link Property2IndexLookup} instead
- */
-@Deprecated
-public class PropertyIndexLookup {
-
-    private final NodeState root;
-
-    public PropertyIndexLookup(NodeState root) {
-        this.root = root;
-    }
-
-    /**
-     * Checks whether the named property is indexed somewhere along the given
-     * path. Lookup starts at the current path (at the root of this object) and
-     * traverses down the path.
-     * 
-     * @param name property name
-     * @param path lookup path
-     * @return true if the property is indexed
-     */
-    public boolean isIndexed(String name, String path) {
-        if (getIndexDefinitionNode(name) != null) {
-            return true;
-        }
-
-        // TODO use PathUtils
-        if (path.startsWith("/")) {
-            path = path.substring(1);
-        }
-        int slash = path.indexOf('/');
-        if (slash == -1) {
-            return false;
-        }
-
-        NodeState child = root.getChildNode(path.substring(0, slash));
-        return new PropertyIndexLookup(child).isIndexed(
-                name, path.substring(slash));
-    }
-
-    /**
-     * Searches for a given <code>String<code> value within this index.
-     * 
-     * <p><b>Note</b> if the property you are looking for is not of type <code>String<code>, 
-     * the converted key value might not match the index key, and there will be no hits on the index.</p>
-     * 
-     * @param name the property name
-     * @param value the property value
-     * @return the set of matched paths
-     */
-    public Set<String> find(String name, String value) {
-        return find(name, PropertyValues.newString(value));
-    }
-
-    /**
-     * Searches for a given value within this index.
-     * 
-     * @param name the property name
-     * @param value the property value
-     * @return the set of matched paths
-     */
-    public Set<String> find(String name, PropertyValue value) {
-        Set<String> paths = Sets.newHashSet();
-
-        PropertyState property;
-        NodeState state = getIndexDefinitionNode(name);
-        if (state != null && state.getChildNode(":index") != null) {
-            state = state.getChildNode(":index");
-            for (String p : PropertyIndex.encode(value)) {
-                property = state.getProperty(p);
-                if (property != null) {
-                    // We have an entry for this value, so use it
-                    for (String path : property.getValue(Type.STRINGS)) {
-                        paths.add(path);
-                    }
-                }
-            }
-        } else {
-            // No index available, so first check this node for a match
-            property = root.getProperty(name);
-            if (property != null) {
-                if (value.isArray()) {
-                    // let query engine handle multi-valued look ups
-                    // simply return all nodes that have this property
-                    paths.add("");
-                } else {
-                    // does it match any of the values of this property?
-                    for (int i = 0; i < property.count(); i++) {
-                        if (property.getValue(value.getType(), i).equals(value.getValue(value.getType()))) {
-                            paths.add("");
-                            // no need to check for more matches in this property
-                            break;
-                        }
-                    }
-                }
-            }
-
-            // ... and then recursively look up from the rest of the tree
-            for (ChildNodeEntry entry : root.getChildNodeEntries()) {
-                String base = entry.getName();
-                PropertyIndexLookup lookup =
-                        new PropertyIndexLookup(entry.getNodeState());
-                for (String path : lookup.find(name, value)) {
-                    if (path.isEmpty()) {
-                        paths.add(base);
-                    } else {
-                        paths.add(base + "/" + path);
-                    }
-                }
-            }
-        }
-
-        return paths;
-    }
-
-    public double getCost(String name, PropertyValue value) {
-        double cost = 0.0;
-        NodeState state = getIndexDefinitionNode(name);
-        if (state != null && state.getChildNode(":index") != null) {
-            state = state.getChildNode(":index");
-            for (String p : PropertyIndex.encode(value)) {
-                PropertyState property = state.getProperty(p);
-                if (property != null) {
-                    cost += property.count();
-                }
-            }
-        } else {
-            cost = Double.POSITIVE_INFINITY;
-        }
-        return cost;
-    }
-
-    /**
-     * Get the node with the index definition node for the given property.
-     * 
-     * @param name the property name
-     * @return the node where the index definition is stored, or null if no
-     *         index definition node was found
-     */
-    @Nullable
-    private NodeState getIndexDefinitionNode(String name) {
-        NodeState state = root.getChildNode(INDEX_DEFINITIONS_NAME);
-        if (state != null) {
-            for (ChildNodeEntry entry : state.getChildNodeEntries()) {
-                PropertyState type = entry.getNodeState().getProperty(IndexConstants.TYPE_PROPERTY_NAME);
-                if(type == null || type.isArray() || !PropertyIndex.TYPE.equals(type.getValue(Type.STRING))){
-                    continue;
-                }
-                PropertyState names = entry.getNodeState().getProperty("propertyNames");
-                if (names != null) {
-                    for (int i = 0; i < names.count(); i++) {
-                        if (name.equals(names.getValue(Type.STRING, i))) {
-                            return entry.getNodeState();
-                        }
-                    }
-                }
-            }
-        }
-        return null;
-    }
-}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java	(working copy)
@@ -1,47 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import java.util.List;
-
-import javax.annotation.Nonnull;
-
-import org.apache.jackrabbit.oak.plugins.index.p2.Property2IndexProvider;
-import org.apache.jackrabbit.oak.spi.query.QueryIndex;
-import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider;
-import org.apache.jackrabbit.oak.spi.state.NodeState;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * A provider for property indexes.
- * <p>
- * Even if there are multiple index definitions, there is only actually one
- * PropertyIndex instance, which is used for all indexes.
- * 
- * @see PropertyIndex
- * @deprecated please use the {@link Property2IndexProvider} instead
- * 
- */
-@Deprecated
-public class PropertyIndexProvider implements QueryIndexProvider {
-
-    @Override @Nonnull
-    public List<QueryIndex> getQueryIndexes(NodeState state) {
-        return ImmutableList.<QueryIndex>of(new PropertyIndex());
-    }
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexHookProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexHookProvider.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexHookProvider.java	(working copy)
@@ -1,54 +0,0 @@
-/*
- * 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.oak.plugins.index.property;
-
-import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.TYPE;
-
-import java.util.List;
-
-import org.apache.felix.scr.annotations.Component;
-import org.apache.felix.scr.annotations.Service;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
-import org.apache.jackrabbit.oak.plugins.index.IndexHookProvider;
-import org.apache.jackrabbit.oak.plugins.index.p2.Property2IndexHookProvider;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-
-import com.google.common.collect.ImmutableList;
-
-/**
- * Service that provides PropertyIndex based IndexHooks.
- * 
- * @see PropertyIndexDiff
- * @see IndexHookProvider
- * @deprecated please use the {@link Property2IndexHookProvider} instead
- * 
- */
-@Component
-@Service(IndexHookProvider.class)
-@Deprecated
-public class PropertyIndexHookProvider implements IndexHookProvider {
-
-    @Override
-    public List<? extends IndexHook> getIndexHooks(String type,
-            NodeBuilder builder) {
-        if (TYPE.equals(type)) {
-            return ImmutableList.of(new PropertyIndexDiff(builder));
-        }
-        return ImmutableList.of();
-    }
-
-}
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/nodetype/NodeTypeIndexLookup.java	(working copy)
@@ -26,7 +26,7 @@
 import com.google.common.collect.Iterables;
 
 /**
- * <code>NodeTypeIndexLookup</code> uses {@link PropertyIndexLookup} internally
+ * <code>NodeTypeIndexLookup</code> uses {@link Property2IndexLookup} internally
  * for cost calculation and queries.
  */
 class NodeTypeIndexLookup implements JcrConstants {
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiff.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiff.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManagerDiff.java	(working copy)
@@ -16,35 +16,28 @@
  */
 package org.apache.jackrabbit.oak.plugins.index;
 
-import java.util.ArrayList;
-import java.util.HashMap;
+import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_UNKNOWN;
+
 import java.util.HashSet;
-import java.util.Iterator;
 import java.util.List;
-import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
 
-import com.google.common.collect.Lists;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
-import org.apache.jackrabbit.oak.commons.PathUtils;
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditor;
+import org.apache.jackrabbit.oak.spi.commit.DefaultEditor;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
-import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
-import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
-import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
-import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_UNKNOWN;
-import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
+import com.google.common.collect.Lists;
 
 /**
  * Acts as a composite NodeStateDiff, it delegates all the diff's events to the
@@ -53,79 +46,22 @@
  * This allows for a simultaneous update of all the indexes via a single
  * traversal of the changes.
  */
-class IndexHookManagerDiff implements NodeStateDiff {
+class IndexHookManagerDiff implements Editor {
 
-    private static final Logger LOG = LoggerFactory
-            .getLogger(IndexHookManagerDiff.class);
-
     private final IndexHookProvider provider;
 
-    private final IndexHookManagerDiff parent;
-
     private final NodeBuilder node;
 
-    private final String nodeName;
+    private Editor inner = new DefaultEditor();
 
-    private String path;
-
-    /**
-     * The map of known indexes.
-     * 
-     * Key: index type name ("p2"). Value: a map from path to index hook.
-     */
-    private final Map<String, Map<String, List<IndexHook>>> indexMap;
-
-    public IndexHookManagerDiff(IndexHookProvider provider, NodeBuilder root,
-            Map<String, Map<String, List<IndexHook>>> updates)
-            throws CommitFailedException {
-        this(provider, null, root, null, "/", updates);
-    }
-
-    private IndexHookManagerDiff(IndexHookProvider provider,
-            IndexHookManagerDiff parent, String nodeName)
-            throws CommitFailedException {
-        this(provider, parent, getChildNode(parent.node, nodeName), nodeName, null,
-                parent.indexMap);
-    }
-
-    private IndexHookManagerDiff(IndexHookProvider provider,
-            IndexHookManagerDiff parent, NodeBuilder node, String name,
-            String path, Map<String, Map<String, List<IndexHook>>> indexMap)
-            throws CommitFailedException {
+    public IndexHookManagerDiff(IndexHookProvider provider, NodeBuilder node) {
         this.provider = provider;
-        this.parent = parent;
         this.node = node;
-        this.nodeName = name;
-        this.path = path;
-        this.indexMap = indexMap;
+    }
 
-        if (node != null && isIndexNodeType(node.getProperty(JCR_PRIMARYTYPE))) {
-            // to prevent double-reindex we only call reindex if:
-            // - the flag exists and is set to true
-            // OR
-            // - the flag does not exist
-            boolean reindex = node.getProperty(REINDEX_PROPERTY_NAME) == null
-                    || node.getProperty(REINDEX_PROPERTY_NAME).getValue(
-                            Type.BOOLEAN);
-            if (reindex) {
-                node.setProperty(REINDEX_PROPERTY_NAME, true);
-                String type = TYPE_UNKNOWN;
-                PropertyState typePS = node.getProperty(TYPE_PROPERTY_NAME);
-                if (typePS != null && !typePS.isArray()) {
-                    type = typePS.getValue(Type.STRING);
-                }
-                // TODO this is kinda fragile
-                NodeBuilder rebuildBuilder = parent.parent.node;
-                String relativePath = PathUtils.getAncestorPath(getPath(), 2);
-                // find parent index by type and trigger a full reindex
-                List<IndexHook> indexes = getIndexesWithRelativePaths(
-                        relativePath, getIndexes(type));
-                for (IndexHook ih : indexes) {
-                    ih.reindex(rebuildBuilder);
-                }
-            }
-        }
-
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
         if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
             Set<String> existingTypes = new HashSet<String>();
             Set<String> reindexTypes = new HashSet<String>();
@@ -134,12 +70,12 @@
             for (String indexName : index.getChildNodeNames()) {
                 NodeBuilder indexChild = index.child(indexName);
                 if (isIndexNodeType(indexChild.getProperty(JCR_PRIMARYTYPE))) {
-                    // this reindex should only happen when the flag is set
-                    // before the index impl is online
-                    boolean reindex = indexChild
-                            .getProperty(REINDEX_PROPERTY_NAME) != null
-                            && indexChild.getProperty(REINDEX_PROPERTY_NAME)
-                                    .getValue(Type.BOOLEAN);
+                    PropertyState reindexPS = indexChild
+                            .getProperty(REINDEX_PROPERTY_NAME);
+                    boolean reindex = reindexPS == null
+                            || (reindexPS != null && indexChild.getProperty(
+                                    REINDEX_PROPERTY_NAME).getValue(
+                                    Type.BOOLEAN));
                     String type = TYPE_UNKNOWN;
                     PropertyState typePS = indexChild
                             .getProperty(TYPE_PROPERTY_NAME);
@@ -154,170 +90,80 @@
             }
             existingTypes.remove(TYPE_UNKNOWN);
             reindexTypes.remove(TYPE_UNKNOWN);
+
+            List<IndexHook> hooks = Lists.newArrayList();
+            List<IndexHook> reindex = Lists.newArrayList();
             for (String type : existingTypes) {
-                Map<String, List<IndexHook>> byType = this.indexMap.get(type);
-                if (byType == null) {
-                    byType = new TreeMap<String, List<IndexHook>>();
-                    this.indexMap.put(type, byType);
-                }
-                List<IndexHook> hooks = byType.get(getPath());
-                if (hooks == null) {
-                    hooks = Lists.newArrayList();
-                    byType.put(getPath(), hooks);
-                }
+                List<? extends IndexHook> hooksTmp = provider.getIndexHooks(
+                        type, node);
+                hooks.addAll(hooksTmp);
                 if (reindexTypes.contains(type)) {
-                    for (IndexHook ih : provider.getIndexHooks(type, node)) {
-                        ih.reindex(node);
-                        // TODO proper cleanup of resources in the case of an
-                        // exception?
-                        hooks.add(ih);
-                    }
-                } else {
-                    hooks.addAll(provider.getIndexHooks(type, node));
+                    reindex.addAll(hooksTmp);
                 }
             }
-        }
-    }
-
-    private static NodeBuilder getChildNode(NodeBuilder node, String nodeName) {
-        if (node != null && node.hasChildNode(nodeName)) {
-            return node.child(nodeName);
-        } else {
-            return null;
-        }
-    }
-
-    private String getPath() {
-        if (path == null) { 
-            // => parent != null
-            path = concat(parent.getPath(), nodeName);
-        }
-        return path;
-    }
-
-    /**
-     * Returns IndexHooks of all types that have the best match (are situated
-     * the closest on the hierarchy) for the given path.
-     */
-    private Map<String, List<IndexHook>> getIndexes() {
-        Map<String, List<IndexHook>> hooks = new HashMap<String, List<IndexHook>>();
-        for (String type : this.indexMap.keySet()) {
-            Map<String, List<IndexHook>> newIndexes = getIndexes(type);
-            for (String key : newIndexes.keySet()) {
-                if (hooks.containsKey(key)) {
-                    hooks.get(key).addAll(newIndexes.get(key));
-                } else {
-                    hooks.put(key, newIndexes.get(key));
+            if (!hooks.isEmpty()) {
+                this.inner = new CompositeEditor(hooks);
+                this.inner.enter(before, after);
+                for (IndexHook ih : reindex) {
+                    ih.reindex(node);
                 }
             }
         }
-        return hooks;
     }
 
-    /**
-     * Returns IndexHooks of the given type that have the best match (are
-     * situated the closest on the hierarchy) for the current path.
-     * 
-     * @param type the index type ("p2")
-     */
-    private Map<String, List<IndexHook>> getIndexes(String type) {
-        Map<String, List<IndexHook>> hooks = new HashMap<String, List<IndexHook>>();
-        Map<String, List<IndexHook>> indexes = this.indexMap.get(type);
-        if (indexes != null && !indexes.isEmpty()) {
-            Iterator<String> iterator = indexes.keySet().iterator();
-            String bestMatch = iterator.next();
-            while (iterator.hasNext()) {
-                String key = iterator.next();
-                if (PathUtils.isAncestor(key, getPath())) {
-                    bestMatch = key;
-                } else {
-                    break;
-                }
-            }
-            List<IndexHook> existing = hooks.get(bestMatch);
-            if (existing == null) {
-                existing = new ArrayList<IndexHook>();
-                hooks.put(bestMatch, existing);
-            }
-            existing.addAll(indexes.get(bestMatch));
-        }
-        return hooks;
+    @Override
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        this.inner.leave(before, after);
     }
 
-    /**
-     * Fixes the relative paths on the best matching indexes so updates apply
-     * properly
-     */
-    private static List<IndexHook> getIndexesWithRelativePaths(String path,
-            Map<String, List<IndexHook>> bestMatches) {
-        List<IndexHook> hooks = new ArrayList<IndexHook>();
-        for (String relativePath : bestMatches.keySet()) {
-            for (IndexHook update : bestMatches.get(relativePath)) {
-                IndexHook u = update;
-                String downPath = path.substring(relativePath.length());
-                for (String p : PathUtils.elements(downPath)) {
-                    u = u.child(p);
-                }
-                hooks.add(u);
-            }
-        }
-        return hooks;
-    }
-
     private static boolean isIndexNodeType(PropertyState ps) {
         return ps != null && !ps.isArray()
                 && ps.getValue(Type.STRING).equals(INDEX_DEFINITIONS_NODE_TYPE);
     }
 
-    // -----------------------------------------------------< NodeStateDiff >---
-
     @Override
-    public void propertyAdded(PropertyState after) {
-        for (IndexHook update : getIndexesWithRelativePaths(getPath(),
-                getIndexes())) {
-            update.propertyAdded(after);
-        }
+    public void propertyAdded(PropertyState after) throws CommitFailedException {
+        inner.propertyAdded(after);
     }
 
     @Override
-    public void propertyChanged(PropertyState before, PropertyState after) {
-        for (IndexHook update : getIndexesWithRelativePaths(getPath(),
-                getIndexes())) {
-            update.propertyChanged(before, after);
-        }
+    public void propertyChanged(PropertyState before, PropertyState after)
+            throws CommitFailedException {
+        inner.propertyChanged(before, after);
     }
 
     @Override
-    public void propertyDeleted(PropertyState before) {
-        for (IndexHook update : getIndexesWithRelativePaths(getPath(),
-                getIndexes())) {
-            update.propertyDeleted(before);
-        }
+    public void propertyDeleted(PropertyState before)
+            throws CommitFailedException {
+        inner.propertyDeleted(before);
     }
 
     @Override
-    public void childNodeAdded(String nodeName, NodeState after) {
-        childNodeChanged(nodeName, EMPTY_NODE, after);
+    public Editor childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
+        if (NodeStateUtils.isHidden(name)) {
+            return null;
+        }
+        return inner.childNodeAdded(name, after);
     }
 
     @Override
-    public void childNodeChanged(String nodeName, NodeState before, NodeState after) {
-        if (NodeStateUtils.isHidden(nodeName)) {
-            return;
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) throws CommitFailedException {
+        if (NodeStateUtils.isHidden(name)) {
+            return null;
         }
-        getIndexesWithRelativePaths(concat(getPath(), nodeName), getIndexes());
-
-        try {
-            after.compareAgainstBaseState(before, new IndexHookManagerDiff(
-                    provider, this, nodeName));
-        } catch (CommitFailedException e) {
-            // TODO ignore exception - is this a hack?
-            LOG.error(e.getMessage(), e);
-        }
+        return inner.childNodeChanged(name, before, after);
     }
 
     @Override
-    public void childNodeDeleted(String nodeName, NodeState before) {
-        childNodeChanged(nodeName, before, EMPTY_NODE);
+    public Editor childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        if (NodeStateUtils.isHidden(name)) {
+            return null;
+        }
+        return inner.childNodeDeleted(name, before);
     }
+
 }
\ No newline at end of file
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/HierarchicalNodeStateDiff.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/HierarchicalNodeStateDiff.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/HierarchicalNodeStateDiff.java	(working copy)
@@ -1,35 +0,0 @@
-/*
- * 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.oak.plugins.index;
-
-import org.apache.jackrabbit.oak.spi.state.NodeStateDiff;
-
-/**
- * TODO document
- */
-public interface HierarchicalNodeStateDiff<T extends HierarchicalNodeStateDiff<T>>
-        extends NodeStateDiff {
-
-    /**
-     * Generates a child HierarchicalNodeStateDiff, based on the current
-     * NodeStateDiff
-     */
-    T child(String name);
-
-    String getPath();
-
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManager.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManager.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHookManager.java	(working copy)
@@ -16,13 +16,9 @@
  */
 package org.apache.jackrabbit.oak.plugins.index;
 
-import java.io.IOException;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.spi.commit.CommitHook;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -37,56 +33,29 @@
  * @see IndexHookProvider
  * 
  */
-public class IndexHookManager implements CommitHook {
+public class IndexHookManager extends EditorHook {
 
-    private final IndexHookProvider provider;
-
     public static final IndexHookManager of(IndexHookProvider provider) {
-        return new IndexHookManager(provider);
+        return new IndexHookManager(new EditorProviderWrapper(provider));
     }
 
-    protected IndexHookManager(IndexHookProvider provider) {
-        this.provider = provider;
-    }
+    private static class EditorProviderWrapper implements EditorProvider {
 
-    @Override
-    public NodeState processCommit(NodeState before, NodeState after)
-            throws CommitFailedException {
-        NodeBuilder builder = after.builder();
+        private final IndexHookProvider provider;
 
-        // key: index type
-        // value: map of path to indexhook 
-        Map<String, Map<String, List<IndexHook>>> indexMap = new HashMap<String, Map<String, List<IndexHook>>>();
-        after.compareAgainstBaseState(before, new IndexHookManagerDiff(
-                provider, builder, indexMap));
-        apply(indexMap);
-        return builder.getNodeState();
-    }
+        EditorProviderWrapper(IndexHookProvider provider) {
+            this.provider = provider;
+        }
 
-    private void apply(Map<String, Map<String, List<IndexHook>>> indexMap)
-            throws CommitFailedException {
-        try {
-            for (String type : indexMap.keySet()) {
-                for (List<IndexHook> hooks : indexMap.get(type).values()) {
-                    for (IndexHook hook : hooks) {
-                        hook.apply();
-                    }
-                }
-            }
-        } finally {
-            for (String type : indexMap.keySet()) {
-                for (List<IndexHook> hooks : indexMap.get(type).values()) {
-                    for (IndexHook hook : hooks) {
-                        try {
-                            hook.close();
-                        } catch (IOException e) {
-                            e.printStackTrace();
-                            throw new CommitFailedException(
-                                    "Failed to close the index hook", e);
-                        }
-                    }
-                }
-            }
+        @Override
+        public Editor getRootEditor(NodeState before, NodeState after,
+                NodeBuilder builder) {
+            return new IndexHookManagerDiff(provider, builder);
         }
+
     }
+
+    protected IndexHookManager(EditorProvider provider) {
+        super(provider);
+    }
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHook.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHook.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexHook.java	(working copy)
@@ -16,30 +16,25 @@
  */
 package org.apache.jackrabbit.oak.plugins.index;
 
-import java.io.Closeable;
-
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 
 /**
  * Represents the content of a QueryIndex as well as a mechanism for keeping
  * this content up to date.
- *<p>
+ * <p>
  * An IndexHook listens for changes to the content and updates the index data
  * accordingly.
  */
-public interface IndexHook extends HierarchicalNodeStateDiff<IndexHook>,
-        Closeable {
+public interface IndexHook extends Editor {
 
     /**
-     * Applies the changes to the index content
-     */
-    void apply() throws CommitFailedException;
-
-    /**
      * Re-create this index.
      * 
-     * @param state the parent of the node "oak:index" (the node that contains the index definition)
+     * @param state
+     *            the parent of the node "oak:index" (the node that contains the
+     *            index definition)
      * @throws CommitFailedException
      */
     void reindex(NodeBuilder state) throws CommitFailedException;
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeEditor.java	(revision 1463488)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/commit/SubtreeEditor.java	(working copy)
@@ -31,7 +31,7 @@
  */
 public class SubtreeEditor extends DefaultEditor {
 
-    private final Editor editor;
+    protected final Editor editor;
 
     private final String[] path;
 
Index: oak-core/pom.xml
===================================================================
--- oak-core/pom.xml	(revision 1463488)
+++ oak-core/pom.xml	(working copy)
@@ -54,7 +54,6 @@
               org.apache.jackrabbit.oak.plugins.commit,
               org.apache.jackrabbit.oak.plugins.index,
               org.apache.jackrabbit.oak.plugins.index.nodetype,
-              org.apache.jackrabbit.oak.plugins.index.property,
               org.apache.jackrabbit.oak.plugins.index.p2,
               org.apache.jackrabbit.oak.plugins.memory,
               org.apache.jackrabbit.oak.plugins.name,
Index: oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHookIT.java
===================================================================
--- oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHookIT.java	(revision 1463488)
+++ oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHookIT.java	(working copy)
@@ -18,12 +18,13 @@
 
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinition;
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinitionImpl;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
-import org.apache.jackrabbit.oak.plugins.index.solr.index.SolrIndexHook;
 import org.apache.jackrabbit.oak.query.ast.Operator;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 import org.apache.jackrabbit.oak.plugins.index.solr.SolrBaseTest;
 import org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.IndexRow;
@@ -55,10 +56,15 @@
         builder.child("newnode").setProperty("prop", "val");
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexHook("/", builder, server);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexHook("/", builder, server);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -67,7 +73,7 @@
         filter.restrictPath("newnode", Filter.PathRestriction.EXACT);
         filter.restrictProperty("prop", Operator.EQUAL,
                 PropertyValues.newString("val"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
         assertNotNull(cursor);
         assertTrue("no results found", cursor.hasNext());
         IndexRow next = cursor.next();
@@ -89,10 +95,15 @@
         builder.setProperty("foo", "bar");
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexHook("/", builder, server);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexHook("/", builder, server);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -100,7 +111,7 @@
         FilterImpl filter = new FilterImpl(null, null);
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
         assertNotNull(cursor);
         assertTrue("no results found", cursor.hasNext());
         IndexRow next = cursor.next();
@@ -128,10 +139,15 @@
 
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexHook("/", builder, server);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexHook("/", builder, server);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -140,7 +156,7 @@
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
         filter.restrictFulltextCondition("bar");
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
 
         assertTrue(cursor.hasNext());
         assertEquals("/", cursor.next().getPath());
Index: oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiffIT.java
===================================================================
--- oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiffIT.java	(revision 1463488)
+++ oak-solr-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiffIT.java	(working copy)
@@ -18,12 +18,14 @@
 
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinition;
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinitionImpl;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
 import org.apache.jackrabbit.oak.plugins.index.solr.index.SolrIndexDiff;
 import org.apache.jackrabbit.oak.query.ast.Operator;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
 import org.apache.jackrabbit.oak.plugins.index.solr.SolrBaseTest;
 import org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.IndexRow;
@@ -55,10 +57,15 @@
         builder.child("newnode").setProperty("prop", "val");
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexDiff(builder, server, configuration);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexDiff(builder, server, configuration);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -67,7 +74,7 @@
         filter.restrictPath("/newnode", Filter.PathRestriction.EXACT);
         filter.restrictProperty("prop", Operator.EQUAL,
                 PropertyValues.newString("val"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
         assertNotNull(cursor);
         assertTrue("no results found", cursor.hasNext());
         IndexRow next = cursor.next();
@@ -89,10 +96,15 @@
         builder.setProperty("foo", "bar");
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexDiff(builder, server, configuration);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexDiff(builder, server, configuration);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -100,7 +112,7 @@
         FilterImpl filter = new FilterImpl(null, null);
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
         assertNotNull(cursor);
         assertTrue("no results found", cursor.hasNext());
         IndexRow next = cursor.next();
@@ -128,10 +140,15 @@
 
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexDiff(builder, server, configuration);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexDiff(builder, server, configuration);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
@@ -140,7 +157,7 @@
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
         filter.restrictFulltextCondition("bar");
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
 
         assertTrue(cursor.hasNext());
         assertEquals("/", cursor.next().getPath());
@@ -167,17 +184,22 @@
 
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new SolrIndexDiff(builder, server, configuration);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new SolrIndexDiff(builder, server, configuration);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("solr",
                 "solr", "/oak:index/solr");
         QueryIndex queryIndex = new SolrQueryIndex(testDef, server, configuration);
         FilterImpl filter = new FilterImpl(null, null);
         filter.restrictPath("/a", Filter.PathRestriction.ALL_CHILDREN);
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
 
         assertTrue(cursor.hasNext());
         assertEquals("/a", cursor.next().getPath());
Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHook.java
===================================================================
--- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHook.java	(revision 1463488)
+++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexHook.java	(working copy)
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.solr.index;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.Collection;
 import java.util.LinkedList;
@@ -26,6 +27,7 @@
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.IndexHook;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -49,7 +51,7 @@
  * @see org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex
  * @see SolrIndexDiff
  */
-public class SolrIndexHook implements IndexHook {
+public class SolrIndexHook implements IndexHook, Closeable {
 
     private static final Logger log = LoggerFactory.getLogger(SolrNodeStateDiff.class);
 
@@ -72,6 +74,10 @@
         this.deleteByIdQueryBuilder = initializeDeleteQueryBuilder();
     }
 
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
+    }
 
     private Collection<SolrInputDocument> docsFromState(String path, @Nonnull NodeState state) {
         List<SolrInputDocument> solrInputDocuments = new LinkedList<SolrInputDocument>();
@@ -118,9 +124,9 @@
     }
 
     @Override
-    public void childNodeAdded(String name, NodeState after) {
+    public Editor childNodeAdded(String name, NodeState after) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         if (exception == null) {
             try {
@@ -129,6 +135,7 @@
                 exception = e;
             }
         }
+        return null;
     }
 
     private void addSubtree(String name, NodeState nodeState) throws IOException {
@@ -136,26 +143,18 @@
     }
 
     @Override
-    public void childNodeChanged(String name, NodeState before, NodeState after) {
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
-        if (exception == null) {
-            try {
-                SolrIndexHook diff = new SolrIndexHook(name, nodebuilder, solrServer);
-                after.compareAgainstBaseState(before, diff);
-                diff.apply();
-            } catch (CommitFailedException e) {
-                exception = new IOException(e);
-            }
-
-        }
+        return new SolrIndexHook(name, nodebuilder, solrServer);
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
+    public Editor childNodeDeleted(String name, NodeState before) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         if (exception == null) {
             try {
@@ -164,6 +163,7 @@
                 exception = e;
             }
         }
+        return null;
     }
 
     private void deleteSubtree(String name, NodeState before) throws IOException {
@@ -178,6 +178,18 @@
     }
 
     @Override
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (exception == null) {
+            try {
+                apply();
+            } catch (CommitFailedException e) {
+                exception = new IOException(e);
+            }
+        }
+
+    }
+
     public void apply() throws CommitFailedException {
         try {
             if (exception != null) {
@@ -241,12 +253,6 @@
         deleteByIdQueryBuilder.delete(4, deleteByIdQueryBuilder.length());
     }
 
-    @Override
-    public IndexHook child(String name) {
-        return new SolrIndexHook(name, nodebuilder, solrServer);
-    }
-
-    @Override
     public String getPath() {
         return path;
     }
Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiff.java
===================================================================
--- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiff.java	(revision 1463488)
+++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexDiff.java	(working copy)
@@ -16,6 +16,7 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.solr.index;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
@@ -26,6 +27,9 @@
 import org.apache.jackrabbit.oak.plugins.index.IndexHook;
 import org.apache.jackrabbit.oak.plugins.index.solr.OakSolrConfiguration;
 import org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
@@ -46,7 +50,7 @@
  * @see org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex
  * @see SolrIndexHook
  */
-public class SolrIndexDiff implements IndexHook {
+public class SolrIndexDiff implements IndexHook, Closeable {
 
     private final SolrIndexDiff parent;
 
@@ -62,8 +66,13 @@
 
     private OakSolrConfiguration configuration;
 
+    /**
+     * the root editor in charge of applying the updates
+     */
+    private final boolean isRoot;
+
     private SolrIndexDiff(SolrIndexDiff parent, NodeBuilder node, SolrServer solrServer,
-                          String name, String path, Map<String, SolrIndexUpdate> updates, OakSolrConfiguration configuration) {
+                          String name, String path, Map<String, SolrIndexUpdate> updates, OakSolrConfiguration configuration, boolean isRoot) {
         this.parent = parent;
         this.node = node;
         this.name = name;
@@ -71,9 +80,22 @@
         this.updates = updates;
         this.solrServer = solrServer;
         this.configuration = configuration;
+        this.isRoot = isRoot;
+    }
 
+    private SolrIndexDiff(SolrIndexDiff parent, SolrServer solrServer, String name) {
+        this(parent, getChildNode(parent.node, name), solrServer, name, null,
+                parent.updates, parent.configuration, false);
+    }
+
+    public SolrIndexDiff(NodeBuilder root, SolrServer solrServer, OakSolrConfiguration configuration) {
+        this(null, root, solrServer, null, "/", new HashMap<String, SolrIndexUpdate>(), configuration, true);
+    }
+
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
         // TODO : test properly on PDFs
-
         if (node != null && node.hasChildNode("oak:index")) {
             NodeBuilder index = node.child("oak:index");
             for (String indexName : index.getChildNodeNames()) {
@@ -91,15 +113,6 @@
         }
     }
 
-    private SolrIndexDiff(SolrIndexDiff parent, SolrServer solrServer, String name) {
-        this(parent, getChildNode(parent.node, name), solrServer, name, null,
-                parent.updates, parent.configuration);
-    }
-
-    public SolrIndexDiff(NodeBuilder root, SolrServer solrServer, OakSolrConfiguration configuration) {
-        this(null, root, solrServer, null, "/", new HashMap<String, SolrIndexUpdate>(), configuration);
-    }
-
     private static NodeBuilder getChildNode(NodeBuilder node, String name) {
         if (node != null && node.hasChildNode(name)) {
             return node.child(name);
@@ -128,8 +141,6 @@
         return isIndexType;
     }
 
-    // -----------------------------------------------------< NodeStateDiff >--
-
     @Override
     public void propertyAdded(PropertyState after) {
         for (SolrIndexUpdate update : updates.values()) {
@@ -152,38 +163,41 @@
     }
 
     @Override
-    public void childNodeAdded(String name, NodeState after) {
+    public Editor childNodeAdded(String name, NodeState after) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         for (SolrIndexUpdate update : updates.values()) {
             update.insert(concat(getPath(), name), new ReadOnlyBuilder(after));
         }
-        after.compareAgainstBaseState(EMPTY_NODE, child(name));
+        return childNodeChanged(name, EMPTY_NODE, after);
     }
 
     @Override
-    public void childNodeChanged(String name, NodeState before, NodeState after) {
+    public Editor childNodeChanged(String name, NodeState before, NodeState after) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
-        after.compareAgainstBaseState(before, child(name));
+        return new SolrIndexDiff(this, solrServer, name);
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
+    public Editor childNodeDeleted(String name, NodeState before) {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         for (SolrIndexUpdate update : updates.values()) {
             update.remove(concat(getPath(), name));
         }
+        return null;
     }
 
-    // -----------------------------------------------------< IndexHook >--
-
     @Override
-    public void apply() throws CommitFailedException {
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if(!isRoot){
+            return;
+        }
         for (SolrIndexUpdate update : updates.values()) {
             update.apply(solrServer);
         }
@@ -198,18 +212,19 @@
             }
         }
         if (reindex) {
-            state.getNodeState().compareAgainstBaseState(
-                    EMPTY_NODE,
-                    new SolrIndexDiff(null, state, solrServer, null, "/", updates, configuration));
+            EditorProvider provider = new EditorProvider() {
+                @Override
+                public Editor getRootEditor(NodeState before, NodeState after,
+                        NodeBuilder builder) {
+                    return new SolrIndexDiff(node, solrServer, configuration);
+                }
+            };
+            EditorHook eh = new EditorHook(provider);
+            eh.processCommit(EMPTY_NODE, state.getNodeState());
         }
     }
 
     @Override
-    public IndexHook child(String name) {
-        return new SolrIndexDiff(this, solrServer, name);
-    }
-
-    @Override
     public void close() throws IOException {
         for (SolrIndexUpdate update : updates.values()) {
             update.close();
Index: oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java
===================================================================
--- oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java	(revision 1463488)
+++ oak-lucene/src/test/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexTest.java	(working copy)
@@ -24,9 +24,11 @@
 
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinition;
 import org.apache.jackrabbit.oak.plugins.index.IndexDefinitionImpl;
-import org.apache.jackrabbit.oak.plugins.index.IndexHook;
 import org.apache.jackrabbit.oak.query.ast.Operator;
 import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.query.Cursor;
 import org.apache.jackrabbit.oak.spi.query.Filter;
 import org.apache.jackrabbit.oak.spi.query.PropertyValues;
@@ -50,10 +52,15 @@
         builder.setProperty("foo", "bar");
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new LuceneIndexDiff(builder);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new LuceneIndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("lucene",
                 TYPE_LUCENE, "/oak:index/lucene");
@@ -62,7 +69,7 @@
         filter.restrictPath("/", Filter.PathRestriction.EXACT);
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
         assertTrue(cursor.hasNext());
         assertEquals("/", cursor.next().getPath());
         assertFalse(cursor.hasNext());
@@ -85,10 +92,15 @@
 
         NodeState after = builder.getNodeState();
 
-        IndexHook l = new LuceneIndexDiff(builder);
-        after.compareAgainstBaseState(before, l);
-        l.apply();
-        l.close();
+        EditorProvider provider = new EditorProvider() {
+            @Override
+            public Editor getRootEditor(NodeState before, NodeState after,
+                    NodeBuilder builder) {
+                return new LuceneIndexDiff(builder);
+            }
+        };
+        EditorHook hook = new EditorHook(provider);
+        NodeState indexed = hook.processCommit(before, after);
 
         IndexDefinition testDef = new IndexDefinitionImpl("lucene",
                 TYPE_LUCENE, "/oak:index/lucene");
@@ -97,7 +109,7 @@
         // filter.restrictPath("/", Filter.PathRestriction.EXACT);
         filter.restrictProperty("foo", Operator.EQUAL,
                 PropertyValues.newString("bar"));
-        Cursor cursor = queryIndex.query(filter, builder.getNodeState());
+        Cursor cursor = queryIndex.query(filter, indexed);
 
         assertTrue(cursor.hasNext());
         assertEquals("/", cursor.next().getPath());
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexDiff.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexDiff.java	(revision 1463488)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexDiff.java	(working copy)
@@ -18,8 +18,13 @@
 
 import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
 import static org.apache.jackrabbit.oak.commons.PathUtils.concat;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NODE_TYPE;
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
 import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE;
 
+import java.io.Closeable;
 import java.io.IOException;
 import java.util.HashMap;
 import java.util.Map;
@@ -28,6 +33,9 @@
 import org.apache.jackrabbit.oak.api.PropertyState;
 import org.apache.jackrabbit.oak.api.Type;
 import org.apache.jackrabbit.oak.plugins.index.IndexHook;
+import org.apache.jackrabbit.oak.spi.commit.Editor;
+import org.apache.jackrabbit.oak.spi.commit.EditorHook;
+import org.apache.jackrabbit.oak.spi.commit.EditorProvider;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 import org.apache.jackrabbit.oak.spi.state.NodeStateUtils;
@@ -43,7 +51,7 @@
  * @see LuceneIndex
  * 
  */
-public class LuceneIndexDiff implements IndexHook, LuceneIndexConstants {
+public class LuceneIndexDiff implements IndexHook, Closeable {
 
     private final LuceneIndexDiff parent;
 
@@ -55,41 +63,33 @@
 
     private final Map<String, LuceneIndexUpdate> updates;
 
+    /**
+     * the root editor in charge of applying the updates
+     */
+    private final boolean isRoot;
+
     private final Parser parser = new AutoDetectParser(
             TikaConfig.getDefaultConfig());
 
     private LuceneIndexDiff(LuceneIndexDiff parent, NodeBuilder node,
-            String name, String path, Map<String, LuceneIndexUpdate> updates) {
+            String name, String path, Map<String, LuceneIndexUpdate> updates,
+            boolean isRoot) {
         this.parent = parent;
         this.node = node;
         this.name = name;
         this.path = path;
         this.updates = updates;
-
-        if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
-            NodeBuilder index = node.child(INDEX_DEFINITIONS_NAME);
-            for (String indexName : index.getChildNodeNames()) {
-                NodeBuilder child = index.child(indexName);
-                if (isIndexNode(child) && !this.updates.containsKey(getPath())) {
-                    this.updates.put(getPath(), new LuceneIndexUpdate(
-                            getPath(), child, parser));
-                }
-            }
-        }
-        if (node != null && name != null && !NodeStateUtils.isHidden(name)) {
-            for (LuceneIndexUpdate update : updates.values()) {
-                update.insert(getPath(), node);
-            }
-        }
+        this.isRoot = isRoot;
     }
 
     private LuceneIndexDiff(LuceneIndexDiff parent, String name) {
         this(parent, getChildNode(parent.node, name), name, null,
-                parent.updates);
+                parent.updates, false);
     }
 
     public LuceneIndexDiff(NodeBuilder root) {
-        this(null, root, null, "/", new HashMap<String, LuceneIndexUpdate>());
+        this(null, root, null, "/", new HashMap<String, LuceneIndexUpdate>(),
+                true);
     }
 
     private static NodeBuilder getChildNode(NodeBuilder node, String name) {
@@ -120,9 +120,38 @@
         return isIndexType;
     }
 
-    // -----------------------------------------------------< NodeStateDiff >--
+    @Override
+    public void enter(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (node != null && node.hasChildNode(INDEX_DEFINITIONS_NAME)) {
+            NodeBuilder index = node.child(INDEX_DEFINITIONS_NAME);
+            for (String indexName : index.getChildNodeNames()) {
+                NodeBuilder child = index.child(indexName);
+                if (isIndexNode(child) && !this.updates.containsKey(getPath())) {
+                    this.updates.put(getPath(), new LuceneIndexUpdate(
+                            getPath(), child, parser));
+                }
+            }
+        }
+        if (node != null && name != null && !NodeStateUtils.isHidden(name)) {
+            for (LuceneIndexUpdate update : updates.values()) {
+                update.insert(getPath(), node);
+            }
+        }
+    }
 
     @Override
+    public void leave(NodeState before, NodeState after)
+            throws CommitFailedException {
+        if (!isRoot) {
+            return;
+        }
+        for (LuceneIndexUpdate update : updates.values()) {
+            update.apply();
+        }
+    }
+
+    @Override
     public void propertyAdded(PropertyState after) {
         for (LuceneIndexUpdate update : updates.values()) {
             update.insert(getPath(), node);
@@ -144,44 +173,39 @@
     }
 
     @Override
-    public void childNodeAdded(String name, NodeState after) {
+    public Editor childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         for (LuceneIndexUpdate update : updates.values()) {
             update.insert(concat(getPath(), name), new ReadOnlyBuilder(after));
         }
-        after.compareAgainstBaseState(EMPTY_NODE, child(name));
+        return childNodeChanged(name, EMPTY_NODE, after);
     }
 
     @Override
-    public void childNodeChanged(String name, NodeState before, NodeState after) {
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) throws CommitFailedException {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
-        after.compareAgainstBaseState(before, child(name));
+        return new LuceneIndexDiff(this, name);
     }
 
     @Override
-    public void childNodeDeleted(String name, NodeState before) {
+    public Editor childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
         if (NodeStateUtils.isHidden(name)) {
-            return;
+            return null;
         }
         for (LuceneIndexUpdate update : updates.values()) {
             update.remove(concat(getPath(), name));
         }
+        return null;
     }
 
-    // -----------------------------------------------------< IndexHook >--
-
     @Override
-    public void apply() throws CommitFailedException {
-        for (LuceneIndexUpdate update : updates.values()) {
-            update.apply();
-        }
-    }
-
-    @Override
     public void reindex(NodeBuilder state) throws CommitFailedException {
         boolean reindex = false;
         for (LuceneIndexUpdate update : updates.values()) {
@@ -190,16 +214,19 @@
             }
         }
         if (reindex) {
-            state.getNodeState().compareAgainstBaseState(
-                    EMPTY_NODE,
-                    new LuceneIndexDiff(null, state, null, "/", updates));
+            EditorProvider provider = new EditorProvider() {
+                @Override
+                public Editor getRootEditor(NodeState before, NodeState after,
+                        NodeBuilder builder) {
+                    return new LuceneIndexDiff(node);
+                }
+            };
+            EditorHook eh = new EditorHook(provider);
+            eh.processCommit(EMPTY_NODE, state.getNodeState());
         }
     }
 
-    @Override
-    public IndexHook child(String name) {
-        return new LuceneIndexDiff(this, name);
-    }
+    // -----------------------------------------------------< Closeable >--
 
     @Override
     public void close() throws IOException {
