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 1464018)
+++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexTest.java	(working copy)
@@ -74,7 +74,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -138,7 +138,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -203,7 +203,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -267,7 +267,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -316,7 +316,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -342,7 +342,7 @@
                 .setProperty("unique", "true")
                 .setProperty("propertyNames", Arrays.asList("foo"),
                         Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                .setProperty(Property2IndexHook.declaringNodeTypes,
                         Arrays.asList("typeFoo"), Type.STRINGS);
         NodeState before = builder.getNodeState();
         builder = before.builder();
@@ -356,7 +356,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -377,7 +377,7 @@
                 .setProperty("unique", "true")
                 .setProperty("propertyNames", Arrays.asList("foo"),
                         Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                .setProperty(Property2IndexHook.declaringNodeTypes,
                         Arrays.asList("typeFoo"), Type.STRINGS);
         NodeState before = builder.getNodeState();
         builder = before.builder();
@@ -391,7 +391,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
@@ -417,7 +417,7 @@
                 .setProperty("unique", "true")
                 .setProperty("propertyNames", Arrays.asList("foo"),
                         Type.STRINGS)
-                .setProperty(Property2IndexDiff.declaringNodeTypes,
+                .setProperty(Property2IndexHook.declaringNodeTypes,
                         Arrays.asList("typeFoo"), Type.STRINGS);
         builder.child("a").setProperty("jcr:primaryType", "typeFoo", Type.NAME)
                 .setProperty("foo", "abc");
@@ -432,7 +432,7 @@
             @Override
             public Editor getRootEditor(NodeState before, NodeState after,
                     NodeBuilder builder) {
-                return new Property2IndexDiff(builder);
+                return new Property2IndexHook(builder);
             }
         };
         EditorHook hook = new EditorHook(provider);
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHook.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHook.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHook.java	(revision 0)
@@ -0,0 +1,328 @@
+/*
+ * 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.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;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+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 com.google.common.collect.ImmutableList;
+
+/**
+ * {@link IndexHook} implementation that is responsible for keeping the
+ * {@link Property2Index} up to date.
+ * <p/>
+ * There is a tree of PropertyIndexDiff objects, each object represents the
+ * changes at a given node.
+ * 
+ * @see Property2Index
+ * @see Property2IndexLookup
+ */
+class Property2IndexHook implements IndexHook, Closeable {
+
+    protected static String propertyNames = "propertyNames";
+
+    protected static String declaringNodeTypes = "declaringNodeTypes";
+
+    private final IndexStoreStrategy store = new ContentMirrorStoreStrategy();
+
+    /**
+     * The parent (null if this is the root node).
+     */
+    private final Property2IndexHook parent;
+
+    /**
+     * The node (never null).
+     */
+    private final NodeBuilder node;
+
+    /**
+     * The node name (the path element). Null for the root node.
+     */
+    private final String nodeName;
+
+    /**
+     * The path of the changed node (built lazily).
+     */
+    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).
+     */
+    private final Map<String, List<Property2IndexHookUpdate>> indexMap;
+
+    /**
+     * the root editor in charge of applying the updates
+     */
+    private final boolean isRoot;
+
+    public Property2IndexHook(NodeBuilder root) {
+        this(null, root, null, "/",
+                new HashMap<String, List<Property2IndexHookUpdate>>(), true);
+    }
+
+    private Property2IndexHook(Property2IndexHook parent, String nodeName) {
+        this(parent, getChildNode(parent.node, nodeName), nodeName, null,
+                parent.indexMap, false);
+    }
+
+    private Property2IndexHook(Property2IndexHook parent, NodeBuilder node,
+            String nodeName, String path,
+            Map<String, List<Property2IndexHookUpdate>> indexMap, boolean isRoot) {
+        this.parent = parent;
+        this.node = node;
+        this.nodeName = nodeName;
+        this.path = path;
+        this.indexMap = indexMap;
+        this.isRoot = isRoot;
+    }
+
+    private static NodeBuilder getChildNode(NodeBuilder node, String name) {
+        if (node != null && node.hasChildNode(name)) {
+            return node.child(name);
+        } else {
+            return null;
+        }
+    }
+
+    public String getPath() {
+        // build the path lazily
+        if (path == null) {
+            path = concat(parent.getPath(), nodeName);
+        }
+        return path;
+    }
+
+    /**
+     * Get all the indexes for the given property name.
+     * 
+     * @param propertyName
+     *            the property name
+     * @return the indexes
+     */
+    private Iterable<Property2IndexHookUpdate> getIndexes(String propertyName) {
+        List<Property2IndexHookUpdate> indexes = indexMap.get(propertyName);
+        if (indexes == null) {
+            return ImmutableList.of();
+        }
+        List<Property2IndexHookUpdate> filtered = new ArrayList<Property2IndexHookUpdate>();
+        for (Property2IndexHookUpdate pi : indexes) {
+            if (node == null || pi.getNodeTypeNames() == null
+                    || pi.getNodeTypeNames().isEmpty()) {
+                filtered.add(pi);
+                continue;
+            }
+            PropertyState ps = node.getProperty(JCR_PRIMARYTYPE);
+            String type = ps != null && !ps.isArray() ? ps
+                    .getValue(Type.STRING) : null;
+            if (type != null) {
+                for (String typeName : pi.getNodeTypeNames()) {
+                    if (typeName.equals(type)) {
+                        filtered.add(pi);
+                        break;
+                    }
+                }
+            }
+        }
+        return filtered;
+    }
+
+    /**
+     * 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
+     */
+    private void addIndexes(NodeBuilder builder, String indexName) {
+        List<String> typeNames = ImmutableList.of();
+        PropertyState appliesTo = builder.getProperty(declaringNodeTypes);
+        if (appliesTo != null) {
+            typeNames = newArrayList(appliesTo.getValue(Type.STRINGS));
+            Collections.sort(typeNames);
+        }
+        PropertyState ps = builder.getProperty(propertyNames);
+
+        Iterable<String> propertyNames = ps != null ? ps.getValue(Type.STRINGS)
+                : ImmutableList.of(indexName);
+        for (String pname : propertyNames) {
+            List<Property2IndexHookUpdate> list = this.indexMap.get(pname);
+            if (list == null) {
+                list = newArrayList();
+                this.indexMap.put(pname, list);
+            }
+            boolean exists = false;
+            String localPath = getPath();
+            for (Property2IndexHookUpdate piu : list) {
+                if (localPath.equals(piu.getPath())
+                        && typeNames.equals(piu.getNodeTypeNames())) {
+                    exists = true;
+                    break;
+                }
+            }
+            if (!exists) {
+                list.add(new Property2IndexHookUpdate(getPath(), builder,
+                        store, typeNames));
+            }
+        }
+    }
+
+    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;
+    }
+
+    @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<Property2IndexHookUpdate> updateList : indexMap.values()) {
+            for (Property2IndexHookUpdate update : updateList) {
+                update.apply();
+            }
+        }
+    }
+
+    @Override
+    public void propertyAdded(PropertyState after) throws CommitFailedException {
+        for (Property2IndexHookUpdate update : getIndexes(after.getName())) {
+            update.insert(getPath(), after);
+        }
+    }
+
+    @Override
+    public void propertyChanged(PropertyState before, PropertyState after)
+            throws CommitFailedException {
+        for (Property2IndexHookUpdate update : getIndexes(after.getName())) {
+            update.remove(getPath(), before);
+            update.insert(getPath(), after);
+        }
+    }
+
+    @Override
+    public void propertyDeleted(PropertyState before)
+            throws CommitFailedException {
+        for (Property2IndexHookUpdate update : getIndexes(before.getName())) {
+            update.remove(getPath(), before);
+        }
+    }
+
+    @Override
+    public Editor childNodeAdded(String name, NodeState after)
+            throws CommitFailedException {
+        return childNodeChanged(name, EMPTY_NODE, after);
+    }
+
+    @Override
+    public Editor childNodeChanged(String name, NodeState before,
+            NodeState after) throws CommitFailedException {
+        if (NodeStateUtils.isHidden(name)) {
+            return null;
+        }
+        return new Property2IndexHook(this, name);
+    }
+
+    @Override
+    public Editor childNodeDeleted(String name, NodeState before)
+            throws CommitFailedException {
+        return childNodeChanged(name, before, EMPTY_NODE);
+    }
+
+    @Override
+    public void reindex(NodeBuilder state) throws CommitFailedException {
+        boolean reindex = false;
+        for (List<Property2IndexHookUpdate> updateList : indexMap.values()) {
+            for (Property2IndexHookUpdate update : updateList) {
+                if (update.getAndResetReindexFlag()) {
+                    reindex = true;
+                }
+            }
+        }
+        if (reindex) {
+            EditorProvider provider = new EditorProvider() {
+                @Override
+                public Editor getRootEditor(NodeState before, NodeState after,
+                        NodeBuilder builder) {
+                    return new Property2IndexHook(node);
+                }
+            };
+            EditorHook eh = new EditorHook(provider);
+            eh.processCommit(EMPTY_NODE, state.getNodeState());
+        }
+    }
+
+    // -----------------------------------------------------< Closeable >--
+
+    @Override
+    public void close() throws IOException {
+        indexMap.clear();
+    }
+}

Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHook.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

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 1464018)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexUpdate.java	(working copy)
@@ -1,173 +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.p2;
-
-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 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.p2.strategy.IndexStoreStrategy;
-import org.apache.jackrabbit.oak.spi.query.PropertyValues;
-import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
-
-import com.google.common.base.Preconditions;
-import com.google.common.collect.Maps;
-import com.google.common.collect.Sets;
-
-/**
- * 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.
- */
-class Property2IndexUpdate {
-
-    private final IndexStoreStrategy store;
-
-    /**
-     * The path of the index definition (where the index data is stored).
-     */
-    private final String path;
-
-    /**
-     * The node types that this index applies to. If <code>null</code> or
-     * <code>empty</code> then the node type of the indexed node is ignored
-     * 
-     */
-    private final List<String> nodeTypeNames;
-
-    /**
-     * 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 Property2IndexUpdate(String path, NodeBuilder node,
-            IndexStoreStrategy store) {
-        this(path, node, store, null);
-    }
-
-    public Property2IndexUpdate(String path, NodeBuilder node,
-            IndexStoreStrategy store, List<String> nodeTypeNames) {
-        this.path = path;
-        this.node = node;
-        this.store = store;
-        this.insert = Maps.newHashMap();
-        this.remove = Maps.newHashMap();
-        this.nodeTypeNames = nodeTypeNames;
-    }
-
-    String getPath() {
-        return path;
-    }
-
-    public List<String> getNodeTypeNames() {
-        return nodeTypeNames;
-    }
-
-    /**
-     * 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 = Property2Index.encode(PropertyValues
-                    .create(value));
-            for (String key : keys) {
-                Set<String> paths = map.get(key);
-                if (paths == null) {
-                    paths = Sets.newHashSet();
-                    map.put(key, paths);
-                }
-                if ("".equals(path)) {
-                    path = "/";
-                }
-                paths.add(path);
-            }
-        }
-    }
-
-    boolean getAndResetReindexFlag() {
-        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;
-    }
-
-    /**
-     * 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()) {
-            store.remove(index, entry.getKey(), entry.getValue());
-        }
-        for (Map.Entry<String, Set<String>> entry : insert.entrySet()) {
-            store.insert(index, entry.getKey(), unique, entry.getValue());
-        }
-    }
-
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookUpdate.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookUpdate.java	(revision 0)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookUpdate.java	(revision 0)
@@ -0,0 +1,155 @@
+/*
+ * 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.p2;
+
+import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.REINDEX_PROPERTY_NAME;
+import static org.apache.jackrabbit.oak.plugins.index.p2.Property2Index.encode;
+
+import java.util.List;
+
+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.p2.strategy.IndexStoreStrategy;
+import org.apache.jackrabbit.oak.spi.query.PropertyValues;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+
+/**
+ * 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.
+ */
+class Property2IndexHookUpdate {
+
+    private final IndexStoreStrategy store;
+
+    /**
+     * The path of the index definition (where the index data is stored).
+     */
+    private final String path;
+
+    /**
+     * The node types that this index applies to. If <code>null</code> or
+     * <code>empty</code> then the node type of the indexed node is ignored
+     * 
+     */
+    private final List<String> nodeTypeNames;
+
+    /**
+     * The node where the index definition is stored.
+     */
+    private final NodeBuilder node;
+
+    /**
+     * The node where the index content is stored.
+     */
+    private final NodeBuilder index;
+
+    private final boolean unique;
+
+    public Property2IndexHookUpdate(String path, NodeBuilder node,
+            IndexStoreStrategy store) {
+        this(path, node, store, null);
+    }
+
+    public Property2IndexHookUpdate(String path, NodeBuilder node,
+            IndexStoreStrategy store, List<String> nodeTypeNames) {
+        this.path = path;
+        this.node = node;
+        this.store = store;
+        this.nodeTypeNames = nodeTypeNames;
+        unique = this.node.getProperty("unique") != null
+                && this.node.getProperty("unique").getValue(Type.BOOLEAN);
+        index = this.node.child(":index");
+    }
+
+    String getPath() {
+        return path;
+    }
+
+    List<String> getNodeTypeNames() {
+        return nodeTypeNames;
+    }
+
+    /**
+     * A property value was added at the given path.
+     * 
+     * @param path
+     *            the path
+     * @param value
+     *            the value
+     */
+    void insert(String path, PropertyState value) throws CommitFailedException {
+        Preconditions.checkArgument(path.startsWith(this.path));
+        if (value.getType().tag() == PropertyType.BINARY) {
+            return;
+        }
+        for (String key : encode(PropertyValues.create(value))) {
+            store.insert(index, key, unique, ImmutableSet.of(trimm(path)));
+        }
+    }
+
+    /**
+     * A property value was removed at the given path.
+     * 
+     * @param path
+     *            the path
+     * @param value
+     *            the value
+     */
+    public void remove(String path, PropertyState value)
+            throws CommitFailedException {
+        Preconditions.checkArgument(path.startsWith(this.path));
+        if (value.getType().tag() == PropertyType.BINARY) {
+            return;
+        }
+        for (String key : encode(PropertyValues.create(value))) {
+            store.remove(index, key, ImmutableSet.of(trimm(path)));
+        }
+    }
+
+    private String trimm(String path) {
+        path = path.substring(this.path.length());
+        if ("".equals(path)) {
+            return "/";
+        }
+        return path;
+    }
+
+    boolean getAndResetReindexFlag() {
+        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;
+    }
+
+    /**
+     * 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 {
+    }
+}

Property changes on: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookUpdate.java
___________________________________________________________________
Added: svn:keywords
   + Author Date Id Revision Rev URL
Added: svn:eol-style
   + native

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 1464018)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexDiff.java	(working copy)
@@ -1,326 +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.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;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-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 com.google.common.collect.ImmutableList;
-
-/**
- * {@link IndexHook} implementation that is responsible for keeping the
- * {@link Property2Index} up to date.
- * <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, Closeable {
-
-    protected static String propertyNames = "propertyNames";
-
-    protected static String declaringNodeTypes = "declaringNodeTypes";
-
-    private final IndexStoreStrategy store = new ContentMirrorStoreStrategy();
-
-    /**
-     * The parent (null if this is the root node).
-     */
-    private final Property2IndexDiff parent;
-
-    /**
-     * The node (never null).
-     */
-    private final NodeBuilder node;
-
-    /**
-     * The node name (the path element). Null for the root node.
-     */
-    private final String nodeName;
-
-    /**
-     * The path of the changed node (built lazily).
-     */
-    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).
-     */
-    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>>(), true);
-    }
-
-    private Property2IndexDiff(Property2IndexDiff parent, String nodeName) {
-        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, boolean isRoot) {
-        this.parent = parent;
-        this.node = node;
-        this.nodeName = nodeName;
-        this.path = path;
-        this.indexMap = indexMap;
-        this.isRoot = isRoot;
-    }
-
-    private static NodeBuilder getChildNode(NodeBuilder node, String name) {
-        if (node != null && node.hasChildNode(name)) {
-            return node.child(name);
-        } else {
-            return null;
-        }
-    }
-
-    public String getPath() {
-        // build the path lazily
-        if (path == null) {
-            path = concat(parent.getPath(), nodeName);
-        }
-        return path;
-    }
-
-    /**
-     * Get all the indexes for the given property name.
-     * 
-     * @param propertyName
-     *            the property name
-     * @return the indexes
-     */
-    private Iterable<Property2IndexUpdate> getIndexes(String propertyName) {
-        List<Property2IndexUpdate> indexes = indexMap.get(propertyName);
-        if (indexes == null) {
-            return ImmutableList.of();
-        }
-        List<Property2IndexUpdate> filtered = new ArrayList<Property2IndexUpdate>();
-        for (Property2IndexUpdate pi : indexes) {
-            if (node == null || pi.getNodeTypeNames() == null
-                    || pi.getNodeTypeNames().isEmpty()) {
-                filtered.add(pi);
-                continue;
-            }
-            PropertyState ps = node.getProperty(JCR_PRIMARYTYPE);
-            String type = ps != null && !ps.isArray() ? ps
-                    .getValue(Type.STRING) : null;
-            if (type != null) {
-                for (String typeName : pi.getNodeTypeNames()) {
-                    if (typeName.equals(type)) {
-                        filtered.add(pi);
-                        break;
-                    }
-                }
-            }
-        }
-        return filtered;
-    }
-
-    /**
-     * 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
-     */
-    private void addIndexes(NodeBuilder builder, String indexName) {
-        List<String> typeNames = ImmutableList.of();
-        PropertyState appliesTo = builder.getProperty(declaringNodeTypes);
-        if (appliesTo != null) {
-            typeNames = newArrayList(appliesTo.getValue(Type.STRINGS));
-            Collections.sort(typeNames);
-        }
-        PropertyState ps = builder.getProperty(propertyNames);
-
-        Iterable<String> propertyNames = ps != null ? ps.getValue(Type.STRINGS)
-                : ImmutableList.of(indexName);
-        for (String pname : propertyNames) {
-            List<Property2IndexUpdate> list = this.indexMap.get(pname);
-            if (list == null) {
-                list = newArrayList();
-                this.indexMap.put(pname, list);
-            }
-            boolean exists = false;
-            String localPath = getPath();
-            for (Property2IndexUpdate piu : list) {
-                if (localPath.equals(piu.getPath())
-                        && typeNames.equals(piu.getNodeTypeNames())) {
-                    exists = true;
-                    break;
-                }
-            }
-            if (!exists) {
-                list.add(new Property2IndexUpdate(getPath(), builder, store,
-                        typeNames));
-            }
-        }
-    }
-
-    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;
-    }
-
-    @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);
-        }
-    }
-
-    @Override
-    public void propertyChanged(PropertyState before, PropertyState after) {
-        for (Property2IndexUpdate update : getIndexes(after.getName())) {
-            update.remove(getPath(), before);
-            update.insert(getPath(), after);
-        }
-    }
-
-    @Override
-    public void propertyDeleted(PropertyState before) {
-        for (Property2IndexUpdate update : getIndexes(before.getName())) {
-            update.remove(getPath(), before);
-        }
-    }
-
-    @Override
-    public Editor childNodeAdded(String name, NodeState after)
-            throws CommitFailedException {
-        return childNodeChanged(name, EMPTY_NODE, after);
-    }
-
-    @Override
-    public Editor childNodeChanged(String name, NodeState before,
-            NodeState after) throws CommitFailedException {
-        if (NodeStateUtils.isHidden(name)) {
-            return null;
-        }
-        return new Property2IndexDiff(this, name);
-    }
-
-    @Override
-    public Editor childNodeDeleted(String name, NodeState before)
-            throws CommitFailedException {
-        return childNodeChanged(name, before, EMPTY_NODE);
-    }
-
-    @Override
-    public void reindex(NodeBuilder state) throws CommitFailedException {
-        boolean reindex = false;
-        for (List<Property2IndexUpdate> updateList : indexMap.values()) {
-            for (Property2IndexUpdate update : updateList) {
-                if (update.getAndResetReindexFlag()) {
-                    reindex = true;
-                }
-            }
-        }
-        if (reindex) {
-            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());
-        }
-    }
-
-    // -----------------------------------------------------< Closeable >--
-
-    @Override
-    public void close() throws IOException {
-        indexMap.clear();
-    }
-}
Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookProvider.java
===================================================================
--- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookProvider.java	(revision 1464018)
+++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/p2/Property2IndexHookProvider.java	(working copy)
@@ -31,7 +31,7 @@
 /**
  * Service that provides PropertyIndex based IndexHooks.
  * 
- * @see Property2IndexDiff
+ * @see Property2IndexHook
  * @see IndexHookProvider
  * 
  */
@@ -43,7 +43,7 @@
     public List<? extends IndexHook> getIndexHooks(String type,
             NodeBuilder builder) {
         if (TYPE.equals(type)) {
-            return ImmutableList.of(new Property2IndexDiff(builder));
+            return ImmutableList.of(new Property2IndexHook(builder));
         }
         return ImmutableList.of();
     }
