Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java	(revision 1558736)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditor.java	(working copy)
@@ -32,7 +32,9 @@
 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.DefaultIndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
@@ -52,7 +54,7 @@
  * 
  * @see LuceneIndex
  */
-public class LuceneIndexEditor implements IndexEditor {
+public class LuceneIndexEditor extends DefaultIndexEditor {
 
     private static final Logger log =
             LoggerFactory.getLogger(LuceneIndexEditor.class);
@@ -158,6 +160,7 @@
             // Remove all index entries in the removed subtree
             writer.deleteDocuments(newPathTerm(path));
             writer.deleteDocuments(new PrefixQuery(newPathTerm(path + "/")));
+            this.context.startedIndexing();
         } catch (IOException e) {
             throw new CommitFailedException(
                     "Lucene", 5, "Failed to remove the index entries of"
@@ -182,7 +185,7 @@
         return false;
     }
 
-    private Document makeDocument(String path, NodeState state, boolean isUpdate) {
+    private Document makeDocument(String path, NodeState state, boolean isUpdate) throws CommitFailedException {
         Document document = new Document();
         boolean dirty = false;
         for (PropertyState property : state.getProperties()) {
@@ -192,11 +195,13 @@
                             .tag())) != 0 && context.includeProperty(pname)) {
                 if (Type.BINARY.tag() == property.getType().tag()) {
                     addBinaryValue(document, property, state);
+                    this.context.startedIndexing();
                     dirty = true;
                 } else {
                     for (String value : property.getValue(Type.STRINGS)) {
                         document.add(newPropertyField(pname, value));
                         document.add(newFulltextField(value));
+                        this.context.startedIndexing();
                         dirty = true;
                     }
                 }
@@ -266,4 +271,9 @@
         return handler.toString();
     }
 
+    @Override
+    public void setIndexUpdateCallback(IndexUpdateCallback updateCallback) {
+        this.context.setUpdateCallback(updateCallback);
+    }
+
 }
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java	(revision 1558736)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorProvider.java	(working copy)
@@ -16,15 +16,14 @@
  */
 package org.apache.jackrabbit.oak.plugins.index.lucene;
 
-import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
 import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.ANALYZER;
+import static org.apache.jackrabbit.oak.plugins.index.lucene.LuceneIndexConstants.TYPE_LUCENE;
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
-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.lucene.analysis.Analyzer;
@@ -46,7 +45,7 @@
     private Analyzer analyzer = ANALYZER;
 
     @Override
-    public Editor getIndexEditor(
+    public IndexEditor getIndexEditor(
             String type, NodeBuilder definition, NodeState root)
             throws CommitFailedException {
         if (TYPE_LUCENE.equals(type)) {
Index: oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java
===================================================================
--- oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java	(revision 1558736)
+++ oak-lucene/src/main/java/org/apache/jackrabbit/oak/plugins/index/lucene/LuceneIndexEditorContext.java	(working copy)
@@ -29,8 +29,10 @@
 
 import javax.jcr.PropertyType;
 
+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.IndexUpdateCallback;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.index.IndexWriter;
@@ -102,6 +104,10 @@
 
     private long indexedNodes;
 
+    private IndexUpdateCallback updateCallback;
+
+    private boolean started = false;
+
     LuceneIndexEditorContext(NodeBuilder definition, Analyzer analyzer) {
         this.definition = definition;
         this.config = getIndexWriterConfig(analyzer);
@@ -166,4 +172,15 @@
         return indexedNodes;
     }
 
+    void setUpdateCallback(IndexUpdateCallback updateCallback) {
+        this.updateCallback = updateCallback;
+    }
+
+    void startedIndexing() throws CommitFailedException {
+        if (this.updateCallback != null && !started) {
+            updateCallback.beforeIndex();
+            started = true;
+        }
+    }
+
 }
Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditor.java
===================================================================
--- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditor.java	(revision 1558736)
+++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditor.java	(working copy)
@@ -24,7 +24,8 @@
 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.plugins.index.IndexEditor;
+import org.apache.jackrabbit.oak.plugins.index.DefaultIndexEditor;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
 import org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfiguration;
 import org.apache.jackrabbit.oak.plugins.index.solr.util.OakSolrUtils;
 import org.apache.jackrabbit.oak.spi.commit.Editor;
@@ -37,7 +38,7 @@
 /**
  * Index editor for keeping a Solr index up to date.
  */
-public class SolrIndexEditor implements IndexEditor {
+public class SolrIndexEditor extends DefaultIndexEditor {
 
     /** Parent editor, or {@code null} if this is the root editor. */
     private final SolrIndexEditor parent;
@@ -57,6 +58,10 @@
 
     private boolean propertiesChanged = false;
 
+    private IndexUpdateCallback updateCallback;
+
+    private boolean startedIndexing = false;
+
     SolrIndexEditor(
             NodeBuilder definition, SolrServer solrServer,
             OakSolrConfiguration configuration) throws CommitFailedException {
@@ -92,6 +97,7 @@
     public void leave(NodeState before, NodeState after)
             throws CommitFailedException {
         if (propertiesChanged || !before.exists()) {
+            startIndexing();
             try {
                 solrServer.add(docFromState(after));
             } catch (SolrServerException e) {
@@ -152,6 +158,7 @@
         try {
             solrServer.deleteByQuery(String.format(
                     "%s:%s\\/*", configuration.getPathField(), path));
+            startIndexing();
         } catch (SolrServerException e) {
             throw new CommitFailedException(
                     "Solr", 5, "Failed to remove documents from Solr", e);
@@ -191,4 +198,20 @@
         return inputDocument;
     }
 
+    @Override
+    public void setIndexUpdateCallback(IndexUpdateCallback updateCallback) {
+        this.updateCallback = updateCallback;
+    }
+
+    protected void startIndexing() throws CommitFailedException {
+        if (parent == null) {
+            if (updateCallback != null && !startedIndexing) {
+                updateCallback.beforeIndex();
+                startedIndexing = true;
+            }
+        } else {
+            parent.startIndexing();
+        }
+    }
+
 }
Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditorProvider.java
===================================================================
--- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditorProvider.java	(revision 1558736)
+++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/index/SolrIndexEditorProvider.java	(working copy)
@@ -17,11 +17,11 @@
 package org.apache.jackrabbit.oak.plugins.index.solr.index;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfigurationProvider;
 import org.apache.jackrabbit.oak.plugins.index.solr.query.SolrQueryIndex;
 import org.apache.jackrabbit.oak.plugins.index.solr.server.SolrServerProvider;
-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.slf4j.Logger;
@@ -49,7 +49,7 @@
     }
 
     @Override
-    public Editor getIndexEditor(
+    public IndexEditor getIndexEditor(
             String type, NodeBuilder definition, NodeState root)
             throws CommitFailedException {
 
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardIndexEditorProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardIndexEditorProvider.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/WhiteboardIndexEditorProvider.java	(working copy)
@@ -20,8 +20,8 @@
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.plugins.index.CompositeIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -38,7 +38,7 @@
     }
 
     @Override
-    public Editor getIndexEditor(
+    public IndexEditor getIndexEditor(
             String type, NodeBuilder builder, NodeState root)
             throws CommitFailedException {
         IndexEditorProvider composite = CompositeIndexEditorProvider
Index: oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/osgi/SolrIndexEditorProviderService.java
===================================================================
--- oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/osgi/SolrIndexEditorProviderService.java	(revision 1558736)
+++ oak-solr-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/solr/osgi/SolrIndexEditorProviderService.java	(working copy)
@@ -26,11 +26,11 @@
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.Service;
 import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.index.solr.configuration.OakSolrConfigurationProvider;
 import org.apache.jackrabbit.oak.plugins.index.solr.index.SolrIndexEditorProvider;
 import org.apache.jackrabbit.oak.plugins.index.solr.server.SolrServerProvider;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -54,9 +54,9 @@
 
     @Override
     @CheckForNull
-    public Editor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, 
+    public IndexEditor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, 
                     @Nonnull NodeState root) throws CommitFailedException {
-        Editor indexEditor = null;
+        IndexEditor indexEditor = null;
         if (solrServerProvider != null && oakSolrConfigurationProvider != null && solrIndexEditorProvider == null) {
             solrIndexEditorProvider = new SolrIndexEditorProvider(solrServerProvider, oakSolrConfigurationProvider);
             indexEditor = solrIndexEditorProvider.getIndexEditor(type, definition, root);
Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdateTest.java
===================================================================
--- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdateTest.java	(revision 1558736)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdateTest.java	(working copy)
@@ -22,6 +22,7 @@
 import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.assertFalse;
 
 import java.util.Set;
 
@@ -86,6 +87,8 @@
         // first check that the index content nodes exist
         checkPathExists(root, INDEX_DEFINITIONS_NAME, "rootIndex",
                 INDEX_CONTENT_NODE_NAME);
+        assertFalse(root.getChildNode(INDEX_DEFINITIONS_NAME).hasChildNode(
+                ":conflict"));
 
         PropertyIndexLookup lookup = new PropertyIndexLookup(root);
         assertEquals(ImmutableSet.of("testRoot"), find(lookup, "foo", "abc"));
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdateCallback.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdateCallback.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdateCallback.java	(revision 0)
@@ -0,0 +1,29 @@
+/*
+ * 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.api.CommitFailedException;
+
+public interface IndexUpdateCallback {
+
+    /**
+     * Called when the indexer finds the first changes that need indexing when
+     * running the diff
+     */
+    void beforeIndex() throws CommitFailedException;
+
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdate.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdate.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUpdate.java	(working copy)
@@ -61,13 +61,25 @@
      */
     private final List<Editor> reindex = newArrayList();
 
+    /**
+     * Callback for the 'before' events of the indexing job
+     */
+    private final IndexUpdateCallback updateCallback;
+
     IndexUpdate(
             IndexEditorProvider provider, String async,
-            NodeState root, NodeBuilder builder) {
+            NodeState root, NodeBuilder builder,
+            IndexUpdateCallback updateCallback) {
         this.provider = checkNotNull(provider);
         this.async = async;
         this.root = checkNotNull(root);
         this.builder = checkNotNull(builder);
+        this.updateCallback = updateCallback;
+    }
+
+    IndexUpdate(IndexEditorProvider provider, String async, NodeState root,
+            NodeBuilder builder) {
+        this(provider, async, root, builder, null);
     }
 
     private IndexUpdate(IndexUpdate parent, String name) {
@@ -76,6 +88,7 @@
         this.async = parent.async;
         this.root = parent.root;
         this.builder = parent.builder.child(checkNotNull(name));
+        this.updateCallback = parent.updateCallback;
     }
 
     @Override
@@ -101,7 +114,7 @@
             NodeBuilder definition = definitions.getChildNode(name);
             if (Objects.equal(async, definition.getString(ASYNC_PROPERTY_NAME))) {
                 String type = definition.getString(TYPE_PROPERTY_NAME);
-                Editor editor = provider.getIndexEditor(type, definition, root);
+                IndexEditor editor = provider.getIndexEditor(type, definition, root);
                 if (editor == null) {
                     // trigger reindexing when an indexer becomes available
                     definition.setProperty(REINDEX_PROPERTY_NAME, true);
@@ -112,8 +125,14 @@
                     for (String rm : definition.getChildNodeNames()) {
                         definition.getChildNode(rm).remove();
                     }
+                    if (updateCallback != null) {
+                        editor.setIndexUpdateCallback(updateCallback);
+                    }
                     reindex.add(VisibleEditor.wrap(editor));
                 } else {
+                    if (updateCallback != null) {
+                        editor.setIndexUpdateCallback(updateCallback);
+                    }
                     editors.add(VisibleEditor.wrap(editor));
                 }
             }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/DefaultIndexEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/DefaultIndexEditor.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/DefaultIndexEditor.java	(revision 0)
@@ -0,0 +1,34 @@
+/*
+ * 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.commit.DefaultEditor;
+
+/**
+ * IndexEditor that does nothing by default and doesn't recurse into subtrees.
+ * Useful as a sentinel or as a base class for more complex editors.
+ * 
+ * @since Oak 0.15
+ */
+public class DefaultIndexEditor extends DefaultEditor implements IndexEditor {
+
+    @Override
+    public void setIndexUpdateCallback(IndexUpdateCallback updateCallback) {
+        //
+    }
+
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditorProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditorProvider.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditorProvider.java	(working copy)
@@ -23,8 +23,6 @@
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.spi.commit.CompositeEditor;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -42,7 +40,7 @@
         if (providers.isEmpty()) {
             return new IndexEditorProvider() {
                 @Override
-                public Editor getIndexEditor(
+                public IndexEditor getIndexEditor(
                         String type, NodeBuilder builder, NodeState root) {
                     return null;
                 }
@@ -66,16 +64,15 @@
     }
 
     @Override
-    public Editor getIndexEditor(
-            String type, NodeBuilder builder, NodeState root)
-            throws CommitFailedException {
-        List<Editor> indexes = Lists.newArrayList();
+    public IndexEditor getIndexEditor(String type, NodeBuilder builder,
+            NodeState root) throws CommitFailedException {
+        List<IndexEditor> indexes = Lists.newArrayList();
         for (IndexEditorProvider provider : providers) {
-            Editor e = provider.getIndexEditor(type, builder, root);
+            IndexEditor e = provider.getIndexEditor(type, builder, root);
             if (e != null) {
                 indexes.add(e);
             }
         }
-        return CompositeEditor.compose(indexes);
+        return CompositeIndexEditor.newIndexEditor(indexes);
     }
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditor.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditor.java	(working copy)
@@ -26,4 +26,6 @@
  */
 public interface IndexEditor extends Editor {
 
+    void setIndexUpdateCallback(IndexUpdateCallback updateCallback);
+
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java	(working copy)
@@ -39,9 +39,8 @@
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
-import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
+import org.apache.jackrabbit.oak.plugins.index.DefaultIndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
-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;
@@ -50,7 +49,7 @@
  * Index editor for keeping a references to a node up to date.
  * 
  */
-class ReferenceEditor extends DefaultEditor implements IndexEditor {
+class ReferenceEditor extends DefaultIndexEditor {
 
     private static ContentMirrorStoreStrategy STORE = new ContentMirrorStoreStrategy();
 
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java	(working copy)
@@ -20,8 +20,8 @@
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -30,7 +30,7 @@
 public class ReferenceEditorProvider implements IndexEditorProvider {
 
     @Override
-    public Editor getIndexEditor(String type, NodeBuilder definition,
+    public IndexEditor getIndexEditor(String type, NodeBuilder definition,
             NodeState root) {
         if (TYPE.equals(type)) {
             return new ReferenceEditor(definition, root);
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditor.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/CompositeIndexEditor.java	(revision 0)
@@ -0,0 +1,60 @@
+/*
+ * 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.base.Preconditions.checkNotNull;
+
+import java.util.Collection;
+
+import javax.annotation.CheckForNull;
+import javax.annotation.Nonnull;
+
+import org.apache.jackrabbit.oak.spi.commit.CompositeEditor;
+
+/**
+ * Aggregation of a list of editors into a single editor.
+ */
+public class CompositeIndexEditor extends CompositeEditor implements
+        IndexEditor {
+
+    @CheckForNull
+    public static IndexEditor newIndexEditor(@Nonnull Collection<IndexEditor> editors) {
+        checkNotNull(editors);
+        switch (editors.size()) {
+        case 0:
+            return null;
+        case 1:
+            return editors.iterator().next();
+        default:
+            return new CompositeIndexEditor(editors);
+        }
+    }
+
+    private final Collection<IndexEditor> editors;
+
+    public CompositeIndexEditor(Collection<IndexEditor> editors) {
+        super(editors);
+        this.editors = editors;
+    }
+
+    @Override
+    public void setIndexUpdateCallback(IndexUpdateCallback updateCallback) {
+        for (IndexEditor editor : editors) {
+            editor.setIndexUpdateCallback(updateCallback);
+        }
+    }
+}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/AsyncIndexUpdate.java	(working copy)
@@ -24,6 +24,7 @@
 
 import java.util.Calendar;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import javax.annotation.Nonnull;
 
@@ -98,8 +99,6 @@
             return;
         }
 
-        preAsyncRun(store);
-
         NodeBuilder builder = store.getRoot().builder();
         NodeBuilder async = builder.child(ASYNC);
 
@@ -112,30 +111,49 @@
             before = MISSING_NODE;
         }
 
-        CommitFailedException exception = EditorDiff.process(new IndexUpdate(
-                provider, name, after, builder), before, after);
+        final AtomicBoolean dirty = new AtomicBoolean(false);
+
+        IndexUpdateCallback iuc = new IndexUpdateCallback() {
+
+            @Override
+            public void beforeIndex() throws CommitFailedException {
+                if (dirty.get()) {
+                    return;
+                }
+                preAsyncRun(store, name);
+                dirty.set(true);
+            }
+        };
+
+        IndexUpdate indexUpdate = new IndexUpdate(provider, name, after,
+                builder, iuc);
+
+        CommitFailedException exception = EditorDiff.process(indexUpdate,
+                before, after);
         if (exception == null) {
-            try {
-                async.setProperty(name, checkpoint);
-                postAsyncRunStatus(builder);
-                store.merge(builder, new CommitHook() {
-                    @Override
-                    @Nonnull
-                    public NodeState processCommit(NodeState before,
-                            NodeState after) throws CommitFailedException {
-                        // check for concurrent updates by this async task
-                        PropertyState stateAfterRebase = before.getChildNode(
-                                ASYNC).getProperty(name);
-                        if (Objects.equal(state, stateAfterRebase)) {
-                            return after;
-                        } else {
-                            throw CONCURRENT_UPDATE;
+            async.setProperty(name, checkpoint);
+            if (dirty.get()) {
+                try {
+                    store.merge(builder, new CommitHook() {
+                        @Override
+                        @Nonnull
+                        public NodeState processCommit(NodeState before,
+                                NodeState after) throws CommitFailedException {
+                            // check for concurrent updates by this async task
+                            PropertyState stateAfterRebase = before
+                                    .getChildNode(ASYNC).getProperty(name);
+                            if (Objects.equal(state, stateAfterRebase)) {
+                                return after;
+                            } else {
+                                throw CONCURRENT_UPDATE;
+                            }
                         }
+                    }, null);
+                    postAsyncRun(store, name);
+                } catch (CommitFailedException e) {
+                    if (e != CONCURRENT_UPDATE) {
+                        exception = e;
                     }
-                }, null);
-            } catch (CommitFailedException e) {
-                if (e != CONCURRENT_UPDATE) {
-                    exception = e;
                 }
             }
         }
@@ -153,17 +171,27 @@
         }
     }
 
-    private void preAsyncRun(NodeStore store) {
+    private static void preAsyncRun(NodeStore store, String name) {
         NodeBuilder builder = store.getRoot().builder();
         preAsyncRunStatus(builder);
         try {
             store.merge(builder, EmptyHook.INSTANCE, null);
         } catch (CommitFailedException e) {
-            log.warn("Index status update {} failed", name, e);
+            log.warn("Index status update {} failed pre-run", name, e);
         }
     }
 
-    private boolean isAlreadyRunning(NodeStore store) {
+    private static void postAsyncRun(NodeStore store, String name) {
+        NodeBuilder builder = store.getRoot().builder();
+        postAsyncRunStatus(builder);
+        try {
+            store.merge(builder, EmptyHook.INSTANCE, null);
+        } catch (CommitFailedException e) {
+            log.warn("Index status update {} failed post-run", name, e);
+        }
+    }
+
+    private static boolean isAlreadyRunning(NodeStore store) {
         NodeState indexState = store.getRoot().getChildNode(IndexConstants.INDEX_DEFINITIONS_NAME);
 
         //Probably the first run
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditorProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditorProvider.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexEditorProvider.java	(working copy)
@@ -20,7 +20,6 @@
 import javax.annotation.Nonnull;
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -47,7 +46,7 @@
      * @return index update editor, or {@code null} if type is unknown
      */
     @CheckForNull
-    Editor getIndexEditor(
+    IndexEditor getIndexEditor(
             @Nonnull String type, @Nonnull NodeBuilder definition,
             @Nonnull NodeState root) throws CommitFailedException;
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java	(working copy)
@@ -35,8 +35,9 @@
 
 import org.apache.jackrabbit.oak.api.CommitFailedException;
 import org.apache.jackrabbit.oak.api.PropertyState;
+import org.apache.jackrabbit.oak.plugins.index.DefaultIndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
-import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
 import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy;
 import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy;
 import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy;
@@ -54,7 +55,7 @@
  * @see PropertyIndex
  * @see PropertyIndexLookup
  */
-class PropertyIndexEditor implements IndexEditor {
+class PropertyIndexEditor extends DefaultIndexEditor {
 
     /** Index storage strategy */
     private static final IndexStoreStrategy MIRROR =
@@ -101,6 +102,10 @@
      */
     private Set<String> afterKeys;
 
+    private IndexUpdateCallback updateCallback;
+
+    private boolean startedIndexing = false;
+
     public PropertyIndexEditor(NodeBuilder definition, NodeState root) {
         this.parent = null;
         this.name = null;
@@ -235,6 +240,7 @@
             }
 
             if (!beforeKeys.isEmpty() || !afterKeys.isEmpty()) {
+                startIndexing();
                 NodeBuilder index = definition.child(INDEX_CONTENT_NODE_NAME);
                 getStrategy(keysToCheckForUniqueness != null).update(
                         index, getPath(), beforeKeys, afterKeys);
@@ -312,4 +318,20 @@
         return new PropertyIndexEditor(this, name);
     }
 
+    @Override
+    public void setIndexUpdateCallback(IndexUpdateCallback updateCallback) {
+        this.updateCallback = updateCallback;
+    }
+
+    protected void startIndexing() throws CommitFailedException {
+        if (parent == null) {
+            if (updateCallback != null && !startedIndexing) {
+                updateCallback.beforeIndex();
+                startedIndexing = true;
+            }
+        } else {
+            parent.startIndexing();
+        }
+    }
+
 }
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java	(revision 1558736)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java	(working copy)
@@ -18,8 +18,8 @@
 
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.plugins.index.IndexEditor;
 import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider;
-import org.apache.jackrabbit.oak.spi.commit.Editor;
 import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
 import org.apache.jackrabbit.oak.spi.state.NodeState;
 
@@ -37,7 +37,7 @@
     public static final String TYPE = "property";
 
     @Override
-    public Editor getIndexEditor(
+    public IndexEditor getIndexEditor(
             String type, NodeBuilder definition, NodeState root) {
         if (TYPE.equals(type)) {
             return new PropertyIndexEditor(definition, root);
