From 418e71c23dc36abae67e9039ba574ac0da776946 Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Fri, 28 Feb 2014 15:16:20 +0100 Subject: [PATCH 1/7] OAK-1263 r1. initial implementation --- .../jackrabbit/oak/plugins/index/IndexUtils.java | 190 ++-- .../oak/plugins/index/property/OrderedIndex.java | 25 + .../index/property/OrderedPropertyIndex.java | 37 + .../index/property/OrderedPropertyIndexEditor.java | 130 +++ .../OrderedPropertyIndexEditorProvider.java | 25 + .../index/property/OrderedPropertyIndexLookup.java | 44 + .../property/OrderedPropertyIndexProvider.java | 44 + .../oak/plugins/index/property/PropertyIndex.java | 49 +- .../index/property/PropertyIndexEditor.java | 55 +- .../index/property/PropertyIndexLookup.java | 76 +- .../strategy/ContentMirrorStoreStrategy.java | 678 +++++++------- .../OrderedContentMirrorStoreStrategy.java | 257 ++++++ .../property/OrderedPropertyIndexEditorTest.java | 67 ++ .../property/OrderedPropertyIndexQueryTest.java | 340 +++++++ .../OrderedContentMirrorStorageStrategyTest.java | 990 +++++++++++++++++++++ .../java/org/apache/jackrabbit/oak/jcr/Jcr.java | 3 + oak-run/pom.xml | 5 +- .../jackrabbit/oak/benchmark/BenchmarkRunner.java | 7 + .../jackrabbit/oak/benchmark/OrderByQueryTest.java | 89 ++ .../oak/benchmark/OrderedIndexBaseTest.java | 112 +++ .../oak/benchmark/OrderedIndexInsertBaseTest.java | 52 ++ .../benchmark/OrderedIndexInsertNoIndexTest.java | 30 + .../OrderedIndexInsertOrderedPropertyTest.java | 39 + .../OrderedIndexInsertStandardPropertyTest.java | 39 + .../oak/benchmark/OrderedIndexQueryBaseTest.java | 80 ++ .../benchmark/OrderedIndexQueryNoIndexTest.java | 29 + .../OrderedIndexQueryOrderedIndexTest.java | 34 + .../OrderedIndexQueryStandardIndexTest.java | 34 + .../oak/benchmark/util/OakIndexUtils.java | 26 +- 29 files changed, 3091 insertions(+), 495 deletions(-) create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java create mode 100644 oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java create mode 100644 oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java create mode 100644 oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java create mode 100644 oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexInsertBaseTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexInsertNoIndexTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexInsertOrderedPropertyTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexInsertStandardPropertyTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexQueryBaseTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexQueryNoIndexTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexQueryOrderedIndexTest.java create mode 100644 oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexQueryStandardIndexTest.java diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java index 7c4e19f..ec920c1 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java @@ -49,95 +49,115 @@ import org.apache.jackrabbit.oak.util.NodeUtil; */ public class IndexUtils { - public static NodeBuilder getOrCreateOakIndex(NodeBuilder root) { - NodeBuilder index; - if (!root.hasChildNode(INDEX_DEFINITIONS_NAME)) { - index = root.child(INDEX_DEFINITIONS_NAME); - // TODO: use property node type name - index.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); - } else { - index = root.child(INDEX_DEFINITIONS_NAME); - } - return index; - } + public static NodeBuilder getOrCreateOakIndex(NodeBuilder root) { + NodeBuilder index; + if (!root.hasChildNode(INDEX_DEFINITIONS_NAME)) { + index = root.child(INDEX_DEFINITIONS_NAME); + // TODO: use property node type name + index.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + } else { + index = root.child(INDEX_DEFINITIONS_NAME); + } + return index; + } - /** - * Create a new property index definition below the given {@code indexNode}. - * - * @param index The oak:index node builder - * @param indexDefName The name of the new property index. - * @param reindex {@code true} if the the reindex flag should be turned on. - * @param unique {@code true} if the index is expected the assert property - * uniqueness. - * @param propertyNames The property names that should be indexed. - * @param declaringNodeTypeNames The declaring node type names or {@code null}. - * @return the NodeBuilder of the new index definition. - */ - public static NodeBuilder createIndexDefinition(@Nonnull NodeBuilder index, - @Nonnull String indexDefName, - boolean reindex, - boolean unique, - @Nonnull Collection propertyNames, - @Nullable Collection declaringNodeTypeNames) { - NodeBuilder entry = index.child(indexDefName) - .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) - .setProperty(REINDEX_PROPERTY_NAME, reindex); - if (unique) { - entry.setProperty(UNIQUE_PROPERTY_NAME, unique); - } - entry.setProperty(PropertyStates.createProperty(PROPERTY_NAMES, propertyNames, NAMES)); - if (declaringNodeTypeNames != null && !declaringNodeTypeNames.isEmpty()) { - entry.setProperty(PropertyStates.createProperty(DECLARING_NODE_TYPES, declaringNodeTypeNames, NAMES)); - } - return entry; - } + /** + * Create a new property index definition below the given {@code indexNode}. + * + * @param index The oak:index node builder + * @param indexDefName The name of the new property index. + * @param reindex {@code true} if the the reindex flag should be turned on. + * @param unique {@code true} if the index is expected the assert property + * uniqueness. + * @param propertyNames The property names that should be indexed. + * @param declaringNodeTypeNames The declaring node type names or {@code null}. + * @return the NodeBuilder of the new index definition. + */ + public static NodeBuilder createIndexDefinition(@Nonnull NodeBuilder index, + @Nonnull String indexDefName, + boolean reindex, + boolean unique, + @Nonnull Collection propertyNames, + @Nullable Collection declaringNodeTypeNames) { + NodeBuilder entry = index.child(indexDefName) + .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) + .setProperty(REINDEX_PROPERTY_NAME, reindex); + if (unique) { + entry.setProperty(UNIQUE_PROPERTY_NAME, unique); + } + entry.setProperty(PropertyStates.createProperty(PROPERTY_NAMES, propertyNames, NAMES)); + if (declaringNodeTypeNames != null && !declaringNodeTypeNames.isEmpty()) { + entry.setProperty(PropertyStates.createProperty(DECLARING_NODE_TYPES, declaringNodeTypeNames, NAMES)); + } + return entry; + } - /** - * Create a new property2 index definition below the given {@code indexNode}. - * - * @param indexNode - * @param indexDefName - * @param unique - * @param propertyNames - * @param declaringNodeTypeNames - */ - public static void createIndexDefinition(@Nonnull NodeUtil indexNode, - @Nonnull String indexDefName, - boolean unique, - @Nonnull String[] propertyNames, - @Nullable String[] declaringNodeTypeNames) throws RepositoryException { - NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); - entry.setString(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE); - entry.setBoolean(REINDEX_PROPERTY_NAME, true); - if (unique) { - entry.setBoolean(UNIQUE_PROPERTY_NAME, true); - } - if (declaringNodeTypeNames != null && declaringNodeTypeNames.length > 0) { - entry.setNames(DECLARING_NODE_TYPES, declaringNodeTypeNames); - } - entry.setNames(PROPERTY_NAMES, propertyNames); - } + /** + * Create a new property2 index definition below the given {@code indexNode}. + * + * @param indexNode + * @param indexDefName + * @param unique + * @param propertyNames + * @param declaringNodeTypeNames + */ + public static void createIndexDefinition(@Nonnull NodeUtil indexNode, + @Nonnull String indexDefName, + boolean unique, + @Nonnull String[] propertyNames, + @Nullable String[] declaringNodeTypeNames) throws RepositoryException { + + createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, PropertyIndexEditorProvider.TYPE); + } - public static void createReferenceIndex(@Nonnull NodeBuilder index) { - index.child(NodeReferenceConstants.NAME) - .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); - } + /** + * Create a new property index definition below the given {@code indexNode} of the provided {@code propertyIndexType}. + * @param indexNode + * @param indexDefName + * @param unique + * @param propertyNames + * @param declaringNodeTypeNames + * @param propertyIndexType + * @throws RepositoryException + */ + public static void createIndexDefinition(@Nonnull NodeUtil indexNode, + @Nonnull String indexDefName, + boolean unique, + @Nonnull String[] propertyNames, + @Nullable String[] declaringNodeTypeNames, + @Nonnull String propertyIndexType) throws RepositoryException { + NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); + entry.setString(TYPE_PROPERTY_NAME, propertyIndexType); + entry.setBoolean(REINDEX_PROPERTY_NAME, true); + if (unique) { + entry.setBoolean(UNIQUE_PROPERTY_NAME, true); + } + if (declaringNodeTypeNames != null && declaringNodeTypeNames.length > 0) { + entry.setNames(DECLARING_NODE_TYPES, declaringNodeTypeNames); + } + entry.setNames(PROPERTY_NAMES, propertyNames); + } - public static boolean isIndexNodeType(NodeState state) { - PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); - return ps != null - && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); - } + public static void createReferenceIndex(@Nonnull NodeBuilder index) { + index.child(NodeReferenceConstants.NAME) + .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); + } - public static boolean isIndexNodeType(NodeState state, String typeIn) { - if (!isIndexNodeType(state)) { - return false; - } - PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); - return type != null && !type.isArray() - && type.getValue(Type.STRING).equals(typeIn); - } + public static boolean isIndexNodeType(NodeState state) { + PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); + return ps != null + && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); + } + + public static boolean isIndexNodeType(NodeState state, String typeIn) { + if (!isIndexNodeType(state)) { + return false; + } + PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); + return type != null && !type.isArray() + && type.getValue(Type.STRING).equals(typeIn); + } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java new file mode 100644 index 0000000..38fa6df --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java @@ -0,0 +1,25 @@ +/* + * 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; + +/** + * interface for shared constants around different actors: QueryIndex, IndexEditors, IndexEditorProviders, ... + */ +public interface OrderedIndex { + String TYPE = "ordered"; +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java new file mode 100644 index 0000000..9a94445 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java @@ -0,0 +1,37 @@ +/* + * 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 org.apache.jackrabbit.oak.spi.state.NodeState; +import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.*; + +/** + * + */ +public class OrderedPropertyIndex extends PropertyIndex { + + @Override + public String getIndexName() { + return TYPE; + } + + @Override + PropertyIndexLookup getLookup(NodeState root) { + return new OrderedPropertyIndexLookup(root); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java new file mode 100644 index 0000000..46807f7 --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java @@ -0,0 +1,130 @@ +package org.apache.jackrabbit.oak.plugins.index.property; + +import java.util.Collections; +import java.util.Set; + +import javax.annotation.Nonnull; + +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.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; +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; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Strings; + +public class OrderedPropertyIndexEditor extends PropertyIndexEditor { + private static final Logger log = LoggerFactory.getLogger(OrderedPropertyIndexEditor.class); + private static final IndexStoreStrategy ORDERED_MIRROR = new OrderedContentMirrorStoreStrategy(); + + private final Set propertyNames; + + private boolean properlyConfigured; + + + public OrderedPropertyIndexEditor(NodeBuilder definition, NodeState root, IndexUpdateCallback callback){ + super(definition,root,callback); + + Set pns = null; + + PropertyState names = definition.getProperty(IndexConstants.PROPERTY_NAMES); + if(names != null){ + String value = names.getValue(Type.NAME,0); + if(Strings.isNullOrEmpty(value)){ + log.warn("Empty value passed as propertyNames. Index not properly configured. Ignoring."); + }else{ + if(names.isArray()){ + log.warn("Only single value supported. '{}' only will be used.", value); + } + pns = Collections.singleton(value); + this.properlyConfigured=true; + } + } + + this.propertyNames = pns; + } + + OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name) { + super(parent,name); + this.propertyNames = parent.getPropertyNames(); + } + + /** + * Same as {@link PropertyIndexEditor#getStrategy(boolean)} but ignores the boolean flag. + * + * @return the proper index strategy + */ + @Override + IndexStoreStrategy getStrategy(boolean unique) { + return ORDERED_MIRROR; + } + + public boolean isProperlyConfigured() { + return properlyConfigured; + } + + @Override + Set getPropertyNames() { + return propertyNames; + } + + @Override + PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, @Nonnull String name) { + return new OrderedPropertyIndexEditor(this, name); + } + + @Override + public void enter(NodeState before, NodeState after) { + log.debug("enter() - before: {} - after: {}",before, after); + super.enter(before, after); + } + + @Override + public void leave(NodeState before, NodeState after) throws CommitFailedException { + log.debug("leave() - before: {} - after: {}",before,after); + super.leave(before, after); + } + + @Override + public void propertyAdded(PropertyState after) { + log.debug("propertyAdded() - after: {}",after); + super.propertyAdded(after); + } + + @Override + public void propertyChanged(PropertyState before, PropertyState after) { + log.debug("propertyChanged() - before: {} - after: {}",before,after); + super.propertyChanged(before, after); + } + + @Override + public void propertyDeleted(PropertyState before) { + log.debug("propertyDeleted() - before: {}", before); + super.propertyDeleted(before); + } + + @Override + public Editor childNodeAdded(String name, NodeState after) { + log.debug("childNodeAdded() - name: {} - after: {}",name,after); + return super.childNodeAdded(name, after); + } + + @Override + public Editor childNodeChanged(String name, NodeState before, NodeState after) { + log.debug("childNodeChanged() - name: {} - before: {} - after: {}",new Object[]{name,before,after}); + return super.childNodeChanged(name, before, after); + } + + @Override + public Editor childNodeDeleted(String name, NodeState before) { + log.debug("childNodeDeleted() - name: {} - before: {}",name,before); + return super.childNodeDeleted(name, before); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java new file mode 100644 index 0000000..75c439d --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java @@ -0,0 +1,25 @@ +package org.apache.jackrabbit.oak.plugins.index.property; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; + +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.IndexEditorProvider; +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; + +@Component +@Service(IndexEditorProvider.class) +public class OrderedPropertyIndexEditorProvider implements IndexEditorProvider, OrderedIndex { + + @Override + @CheckForNull + public Editor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, @Nonnull NodeState root, @Nonnull IndexUpdateCallback callback) throws CommitFailedException { + Editor _editor = (TYPE.equals(type)) ? new OrderedPropertyIndexEditor(definition,root,callback) : null; + return _editor; + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java new file mode 100644 index 0000000..9a84e4f --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexLookup.java @@ -0,0 +1,44 @@ +/* + * 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 org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +/** + * + */ +public class OrderedPropertyIndexLookup extends PropertyIndexLookup { + + private static final IndexStoreStrategy STORE = new OrderedContentMirrorStoreStrategy(); + + public OrderedPropertyIndexLookup(NodeState root) { + super(root); + } + + @Override + IndexStoreStrategy getStrategy(NodeState indexMeta) { + return STORE; + } + + @Override + String getType() { + return OrderedIndex.TYPE; + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java new file mode 100644 index 0000000..15daaed --- /dev/null +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java @@ -0,0 +1,44 @@ +/* + * 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.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Service; +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; + +@Component +@Service(QueryIndexProvider.class) +public class OrderedPropertyIndexProvider implements QueryIndexProvider { + + /* (non-Javadoc) + * @see org.apache.jackrabbit.oak.spi.query.QueryIndexProvider#getQueryIndexes(org.apache.jackrabbit.oak.spi.state.NodeState) + */ + @Override + @Nonnull + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList.of(new OrderedPropertyIndex()); + } +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java index e7c19c0..dd36ba6 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java @@ -36,10 +36,9 @@ import com.google.common.collect.Iterables; /** * Provides a QueryIndex that does lookups against a property index - * + * *

- * To define a property index on a subtree you have to add an oak:index node. - *
+ * To define a property index on a subtree you have to add an oak:index node.
* Next (as a child node) follows the index definition node that: *

    *
  • must be of type oak:QueryIndexDefinition
  • @@ -49,15 +48,17 @@ import com.google.common.collect.Iterables; *

    *

    * Optionally you can specify - *

      - *
    • a uniqueness constraint on a property index by setting the unique flag to true
    • - *
    • that the property index only applies to a certain node type by setting the declaringNodeTypes property
    • + *
        + *
      • a uniqueness constraint on a property index by setting the unique flag to true
      • + *
      • that the property index only applies to a certain node type by setting the declaringNodeTypes + * property
      • *
      *

      *

      * Notes: *

        - *
      • propertyNames 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
      • + *
      • propertyNames 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
      • *
      • reindex is a property that when set to true, triggers a full content reindex.
      • *
      *

      @@ -113,13 +114,23 @@ class PropertyIndex implements QueryIndex { return values; } - //--------------------------------------------------------< QueryIndex >-- + // --------------------------------------------------------< QueryIndex >-- @Override public String getIndexName() { return "property"; } + /** + * return the proper implementation of the Lookup + * + * @param root + * @return + */ + PropertyIndexLookup getLookup(NodeState root) { + return new PropertyIndexLookup(root); + } + @Override public double getCost(Filter filter, NodeState root) { if (filter.getFullTextConstraint() != null) { @@ -127,14 +138,13 @@ class PropertyIndex implements QueryIndex { return Double.POSITIVE_INFINITY; } - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); // TODO support indexes on a path // currently, only indexes on the root node are supported if (lookup.isIndexed(propertyName, "/", filter)) { - if (pr.firstIncluding && pr.lastIncluding - && pr.first != null && pr.first.equals(pr.last)) { + if (pr.firstIncluding && pr.lastIncluding && pr.first != null && pr.first.equals(pr.last)) { // "[property] = $value" return lookup.getCost(filter, propertyName, pr.first); } else if (pr.list != null) { @@ -157,7 +167,7 @@ class PropertyIndex implements QueryIndex { public Cursor query(Filter filter, NodeState root) { Iterable paths = null; - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); int depth = 1; for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); @@ -166,8 +176,7 @@ class PropertyIndex implements QueryIndex { // currently, only indexes on the root node are supported if (lookup.isIndexed(propertyName, "/", filter)) { // equality - if (pr.firstIncluding && pr.lastIncluding - && pr.first != null && pr.first.equals(pr.last)) { + if (pr.firstIncluding && pr.lastIncluding && pr.first != null && pr.first.equals(pr.last)) { // "[property] = $value" paths = lookup.query(filter, propertyName, pr.first); break; @@ -189,7 +198,8 @@ class PropertyIndex implements QueryIndex { } } if (paths == null) { - throw new IllegalStateException("Property index is used even when no index is available for filter " + filter); + throw new IllegalStateException("Property index is used even when no index is available for filter " + + filter); } Cursor c = Cursors.newPathCursor(paths); if (depth > 1) { @@ -197,19 +207,18 @@ class PropertyIndex implements QueryIndex { } return c; } - + @Override public String getPlan(Filter filter, NodeState root) { StringBuilder buff = new StringBuilder("property"); StringBuilder notIndexed = new StringBuilder(); - PropertyIndexLookup lookup = new PropertyIndexLookup(root); + PropertyIndexLookup lookup = getLookup(root); for (PropertyRestriction pr : filter.getPropertyRestrictions()) { String propertyName = PathUtils.getName(pr.propertyName); // TODO support indexes on a path // currently, only indexes on the root node are supported if (lookup.isIndexed(propertyName, "/", filter)) { - if (pr.firstIncluding && pr.lastIncluding - && pr.first != null && pr.first.equals(pr.last)) { + if (pr.firstIncluding && pr.lastIncluding && pr.first != null && pr.first.equals(pr.last)) { buff.append(' ').append(propertyName).append('=').append(pr.first); } else { buff.append(' ').append(propertyName); @@ -237,4 +246,4 @@ class PropertyIndex implements QueryIndex { return buff.toString(); } -} \ No newline at end of file +} diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java index ddfbe13..16ea189 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java @@ -31,6 +31,7 @@ import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.enc import java.util.Set; +import javax.annotation.Nonnull; import javax.jcr.PropertyType; import org.apache.jackrabbit.oak.api.CommitFailedException; @@ -56,7 +57,6 @@ import com.google.common.base.Predicate; * @see PropertyIndexLookup */ class PropertyIndexEditor implements IndexEditor { - /** Index storage strategy */ private static final IndexStoreStrategy MIRROR = new ContentMirrorStoreStrategy(); @@ -79,7 +79,7 @@ class PropertyIndexEditor implements IndexEditor { private final Set propertyNames; - /** Type predicate, or {@code null} if there are no type restrictions */ + /** Type predicate, or {@code null} if there are no type restrictions */ private final Predicate typePredicate; /** @@ -111,6 +111,8 @@ class PropertyIndexEditor implements IndexEditor { this.path = "/"; this.definition = definition; + //initPropertyNames(definition); + // get property names PropertyState names = definition.getProperty(PROPERTY_NAMES); if (names.count() == 1) { @@ -119,7 +121,7 @@ class PropertyIndexEditor implements IndexEditor { } else { this.propertyNames = newHashSet(names.getValue(NAMES)); } - + // get declaring types, and all their subtypes // TODO: should we reindex when type definitions change? if (definition.hasProperty(DECLARING_NODE_TYPES)) { @@ -137,17 +139,26 @@ class PropertyIndexEditor implements IndexEditor { } this.updateCallback = updateCallback; } - - private PropertyIndexEditor(PropertyIndexEditor parent, String name) { + + PropertyIndexEditor(PropertyIndexEditor parent, String name) { this.parent = parent; this.name = name; this.path = null; this.definition = parent.definition; - this.propertyNames = parent.propertyNames; + this.propertyNames = parent.getPropertyNames(); this.typePredicate = parent.typePredicate; this.keysToCheckForUniqueness = parent.keysToCheckForUniqueness; this.updateCallback = parent.updateCallback; } + + /** + * commodity method for allowing extensions + * + * @return + */ + Set getPropertyNames() { + return propertyNames; + } /** * Returns the path of this node, building it lazily when first requested. @@ -193,7 +204,7 @@ class PropertyIndexEditor implements IndexEditor { return keys; } - private static IndexStoreStrategy getStrategy(boolean unique) { + IndexStoreStrategy getStrategy(boolean unique) { return unique ? UNIQUE : MIRROR; } @@ -214,8 +225,8 @@ class PropertyIndexEditor implements IndexEditor { if (typeChanged) { // possible type change, so ignore diff results and // just load all matching values from both states - beforeKeys = getMatchingKeys(before, propertyNames); - afterKeys = getMatchingKeys(after, propertyNames); + beforeKeys = getMatchingKeys(before, getPropertyNames()); + afterKeys = getMatchingKeys(after, getPropertyNames()); } if (beforeKeys != null && !typePredicate.apply(before)) { // the before state doesn't match the type, so clear its values @@ -282,7 +293,7 @@ class PropertyIndexEditor implements IndexEditor { public void propertyAdded(PropertyState after) { String name = after.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { afterKeys = addValueKeys(afterKeys, after); } } @@ -291,7 +302,7 @@ class PropertyIndexEditor implements IndexEditor { public void propertyChanged(PropertyState before, PropertyState after) { String name = after.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { beforeKeys = addValueKeys(beforeKeys, before); afterKeys = addValueKeys(afterKeys, after); } @@ -301,25 +312,35 @@ class PropertyIndexEditor implements IndexEditor { public void propertyDeleted(PropertyState before) { String name = before.getName(); typeChanged = typeChanged || isTypeProperty(name); - if (propertyNames.contains(name)) { + if (getPropertyNames().contains(name)) { beforeKeys = addValueKeys(beforeKeys, before); } } + /** + * Retrieve a new index editor associated with the child node to process + * + * @param parent the index editor related to the parent node + * @param name the name of the child node + * @return an instance of the PropertyIndexEditor + */ + PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, @Nonnull String name){ + return new PropertyIndexEditor(parent, name); + } + @Override public Editor childNodeAdded(String name, NodeState after) { - return new PropertyIndexEditor(this, name); + return getChildIndexEditor(this, name); } @Override - public Editor childNodeChanged( - String name, NodeState before, NodeState after) { - return new PropertyIndexEditor(this, name); + public Editor childNodeChanged(String name, NodeState before, NodeState after) { + return getChildIndexEditor(this, name); } @Override public Editor childNodeDeleted(String name, NodeState before) { - return new PropertyIndexEditor(this, name); + return getChildIndexEditor(this, name); } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java index 271e101..a2ae9d1 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java @@ -18,14 +18,13 @@ package org.apache.jackrabbit.oak.plugins.index.property; import static com.google.common.collect.Iterables.contains; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.DECLARING_NODE_TYPES; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.PROPERTY_NAMES; import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.TYPE_PROPERTY_NAME; -import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME; -import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider.TYPE; import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode; +import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider.TYPE; -import java.util.Iterator; import java.util.Set; import javax.annotation.Nullable; @@ -43,10 +42,8 @@ import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; /** - * Is responsible for querying the property index content. - *
      - * This class can be used directly on a subtree where there is an index defined - * by supplying a {@link NodeState} root. + * Is responsible for querying the property index content.
      + * This class can be used directly on a subtree where there is an index defined by supplying a {@link NodeState} root. * *
        * 
      @@ -64,19 +61,17 @@ public class PropertyIndexLookup {
            * The cost overhead to use the index in number of read operations.
            */
           private static final int COST_OVERHEAD = 2;
      -    
      +
           /**
            * The maximum cost when the index can be used.
            */
           private static final int MAX_COST = 100;
       
           /** Index storage strategy */
      -    private static final IndexStoreStrategy MIRROR =
      -            new ContentMirrorStoreStrategy();
      +    private static final IndexStoreStrategy MIRROR = new ContentMirrorStoreStrategy();
       
           /** Index storage strategy */
      -    private static final IndexStoreStrategy UNIQUE =
      -            new UniqueEntryStoreStrategy();
      +    private static final IndexStoreStrategy UNIQUE = new UniqueEntryStoreStrategy();
       
           private final NodeState root;
       
      @@ -85,13 +80,15 @@ public class PropertyIndexLookup {
           }
       
           /**
      -     * 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.
      +     * 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 propertyName property name
      -     * @param path lookup path
      -     * @param filter for the node type restriction (null if no node type restriction)
      +     * @param propertyName
      +     *            property name
      +     * @param path
      +     *            lookup path
      +     * @param filter
      +     *            for the node type restriction (null if no node type restriction)
            * @return true if the property is indexed
            */
           public boolean isIndexed(String propertyName, String path, Filter filter) {
      @@ -100,12 +97,11 @@ public class PropertyIndexLookup {
               }
       
               NodeState node = root;
      -        Iterator it = PathUtils.elements(path).iterator();
      -        while (it.hasNext()) {
      +        for(String s : PathUtils.elements(path)){
                   if (getIndexNode(node, propertyName, filter) != null) {
                       return true;
                   }
      -            node = node.getChildNode(it.next());
      +            node = node.getChildNode(s);
               }
               return false;
           }
      @@ -117,8 +113,8 @@ public class PropertyIndexLookup {
               }
               return getStrategy(indexMeta).query(filter, propertyName, indexMeta, encode(value));
           }
      -        
      -    private static IndexStoreStrategy getStrategy(NodeState indexMeta) {
      +
      +    IndexStoreStrategy getStrategy(NodeState indexMeta) {
               if (indexMeta.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) {
                   return UNIQUE;
               }
      @@ -130,24 +126,21 @@ public class PropertyIndexLookup {
               if (indexMeta == null) {
                   return Double.POSITIVE_INFINITY;
               }
      -        return COST_OVERHEAD + 
      -                getStrategy(indexMeta).count(indexMeta, encode(value), MAX_COST);
      +        return COST_OVERHEAD + getStrategy(indexMeta).count(indexMeta, encode(value), MAX_COST);
           }
       
           /**
      -     * Get the node with the index definition for the given property, if there
      -     * is an applicable index with data.
      +     * Get the node with the index definition for the given property, if there is an applicable index with data.
            * 
      -     * @param propertyName the property name
      -     * @param filter the filter (which contains information of all supertypes,
      -     *            unless the filter matches all types)
      -     * @return the node where the index definition (metadata) is stored (the
      -     *         parent of ":index"), or null if no index definition or index data
      -     *         node was found
      +     * @param propertyName
      +     *            the property name
      +     * @param filter
      +     *            the filter (which contains information of all supertypes, unless the filter matches all types)
      +     * @return the node where the index definition (metadata) is stored (the parent of ":index"), or null if no index
      +     *         definition or index data node was found
            */
           @Nullable
      -    private static NodeState getIndexNode(
      -            NodeState node, String propertyName, Filter filter) {
      +    private NodeState getIndexNode(NodeState node, String propertyName, Filter filter) {
               // keep a fallback to a matching index def that has *no* node type constraints
               // (initially, there is no fallback)
               NodeState fallback = null;
      @@ -156,7 +149,7 @@ public class PropertyIndexLookup {
               for (ChildNodeEntry entry : state.getChildNodeEntries()) {
                   NodeState index = entry.getNodeState();
                   PropertyState type = index.getProperty(TYPE_PROPERTY_NAME);
      -            if (type == null || type.isArray() || !TYPE.equals(type.getValue(Type.STRING))) {
      +            if (type == null || type.isArray() || !getType().equals(type.getValue(Type.STRING))) {
                       continue;
                   }
                   if (contains(index.getNames(PROPERTY_NAMES), propertyName)) {
      @@ -184,7 +177,16 @@ public class PropertyIndexLookup {
               }
               return fallback;
           }
      -    
      +
      +    /**
      +     * retrieve the type of the index
      +     * 
      +     * @return
      +     */
      +    String getType() {
      +        return TYPE;
      +    }
      +
           private static Set getSuperTypes(Filter filter) {
               if (filter != null && !filter.matchesAllTypes()) {
                   return filter.getSupertypes();
      diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
      index cac52cf..95b640c 100644
      --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
      +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
      @@ -24,6 +24,8 @@ import java.util.Deque;
       import java.util.Iterator;
       import java.util.Set;
       
      +import javax.annotation.Nonnull;
      +
       import org.apache.jackrabbit.oak.api.PropertyState;
       import org.apache.jackrabbit.oak.api.Type;
       import org.apache.jackrabbit.oak.commons.PathUtils;
      @@ -65,344 +67,370 @@ import com.google.common.collect.Sets;
        */
       public class ContentMirrorStoreStrategy implements IndexStoreStrategy {
       
      -    static final Logger LOG = LoggerFactory.getLogger(ContentMirrorStoreStrategy.class);
      -
      -    @Override
      -    public void update(
      -            NodeBuilder index, String path,
      -            Set beforeKeys, Set afterKeys) {
      -        for (String key : beforeKeys) {
      -            remove(index, key, path);
      -        }
      -        for (String key : afterKeys) {
      -            insert(index, key, path);
      -        }
      -    }
      -
      -    private static void remove(NodeBuilder index, String key, String value) {
      -        NodeBuilder builder = index.getChildNode(key);
      -        if (builder.exists()) {
      -            // Collect all builders along the given path
      -            Deque builders = newArrayDeque();
      +   static final Logger LOG = LoggerFactory.getLogger(ContentMirrorStoreStrategy.class);
      +
      +   @Override
      +   public void update(
      +         NodeBuilder index, String path,
      +         Set beforeKeys, Set afterKeys) {
      +      for (String key : beforeKeys) {
      +         remove(index, key, path);
      +      }
      +      for (String key : afterKeys) {
      +         insert(index, key, path);
      +      }
      +   }
      +
      +   private void remove(NodeBuilder index, String key, String value) {
      +      NodeBuilder builder = index.getChildNode(key);
      +      if (builder.exists()) {
      +         // Collect all builders along the given path
      +         Deque builders = newArrayDeque();
      +         builders.addFirst(builder);
      +
      +         // Descend to the correct location in the index tree
      +         for (String name : PathUtils.elements(value)) {
      +            builder = builder.getChildNode(name);
                   builders.addFirst(builder);
      +         }
       
      -            // Descend to the correct location in the index tree
      -            for (String name : PathUtils.elements(value)) {
      -                builder = builder.getChildNode(name);
      -                builders.addFirst(builder);
      -            }
      +         // Drop the match value,  if present
      +         if (builder.exists()) {
      +            builder.removeProperty("match");
      +         }
       
      -            // Drop the match value,  if present
      -            if (builder.exists()) {
      -                builder.removeProperty("match");
      -            }
      +         // Prune all index nodes that are no longer needed
      +         prune(index, builders);
      +      }
      +   }
       
      -            // Prune all index nodes that are no longer needed
      -            for (NodeBuilder node : builders) {
      -                if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) {
      -                    return;
      -                } else if (node.exists()) {
      -                    node.remove();
      -                }
      -            }
      -        }
      -    }
      -
      -    private static void insert(NodeBuilder index, String key, String value) {
      -        NodeBuilder builder = index.child(key);
      -        for (String name : PathUtils.elements(value)) {
      -            builder = builder.child(name);
      -        }
      -        builder.setProperty("match", true);
      -    }
      -
      -    public Iterable query(final Filter filter, final String indexName,
      -            final NodeState indexMeta, final String indexStorageNodeName,
      -            final Iterable values) {
      -        final NodeState index = indexMeta.getChildNode(indexStorageNodeName);
      -        return new Iterable() {
      -            @Override
      -            public Iterator iterator() {
      -                PathIterator it = new PathIterator(filter, indexName);
      -                if (values == null) {
      -                    it.setPathContainsValue(true);
      -                    it.enqueue(index.getChildNodeEntries().iterator());
      -                } else {
      -                    for (String p : values) {
      -                        NodeState property = index.getChildNode(p);
      -                        if (property.exists()) {
      -                            // we have an entry for this value, so use it
      -                            it.enqueue(Iterators.singletonIterator(
      -                                    new MemoryChildNodeEntry("", property)));
      -                        }
      -                    }
      -                }
      -                return it;
      -            }
      -        };
      -    }
      -
      -    @Override
      -    public Iterable query(final Filter filter, final String indexName, 
      -            final NodeState indexMeta, final Iterable values) {
      -        return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values);
      -    }
      -
      -    @Override
      -    public long count(NodeState indexMeta, Set values, int max) {
      -        return count(indexMeta, INDEX_CONTENT_NODE_NAME, values, max);
      -    }
      -
      -    public long count(NodeState indexMeta, final String indexStorageNodeName,
      -            Set values, int max) {
      -        NodeState index = indexMeta.getChildNode(indexStorageNodeName);
      -        int count = 0;
      -        if (values == null) {
      -            PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME);
      -            if (ec != null) {
      -                return ec.getValue(Type.LONG);
      -            }
      -            CountingNodeVisitor v = new CountingNodeVisitor(max);
      -            v.visit(index);
      -            count = v.getEstimatedCount();
      -            // "is not null" queries typically read more data
      -            count *= 10;
      -        } else {
      -            int size = values.size();
      -            if (size == 0) {
      -                return 0;
      -            }
      -            max = Math.max(10, max / size);
      -            int i = 0;
      -            for (String p : values) {
      -                if (count > max && i > 3) {
      -                    // the total count is extrapolated from the the number 
      -                    // of values counted so far to the total number of values
      -                    count = count * size / i;
      -                    break;
      -                }
      -                NodeState s = index.getChildNode(p);
      -                if (s.exists()) {
      -                    CountingNodeVisitor v = new CountingNodeVisitor(max);
      -                    v.visit(s);
      -                    count += v.getEstimatedCount();
      -                }
      -                i++;
      -            }
      -        }
      -        return count;
      -    }
      -
      -    /**
      -     * An iterator over paths within an index node.
      -     */
      -    static class PathIterator implements Iterator {
      -        
      -        private final Filter filter;
      -        private final String indexName;
      -        private final Deque> nodeIterators =
      -                Queues.newArrayDeque();
      -        private int readCount;
      -        private boolean init;
      -        private boolean closed;
      -        private String parentPath;
      -        private String currentPath;
      -        private boolean pathContainsValue;
      -        
      -        /**
      -         * Keep the returned path, to avoid returning duplicate entries.
      -         */
      -        private final Set knownPaths = Sets.newHashSet();
      -        
      -        PathIterator(Filter filter, String indexName) {
      -            this.filter = filter;
      -            this.indexName = indexName;
      -            parentPath = "";
      -            currentPath = "/";
      -        }
      -        
      -        void enqueue(Iterator it) {
      -            nodeIterators.addLast(it);
      -        }
      -        
      -        void setPathContainsValue(boolean pathContainsValue) {
      -            if (init) {
      -                throw new IllegalStateException("This iterator is already initialized");
      -            }
      -            this.pathContainsValue = pathContainsValue;
      -        }
      -
      -        @Override
      -        public boolean hasNext() {
      -            if (!closed && !init) {
      -                fetchNext();
      -                init = true;
      +   /**
      +    * Physically prune a list of nodes from the index
      +    * 
      +    * @param index the current index
      +    * @param builders list of nodes to prune
      +    */
      +   void prune(final NodeBuilder index, final Deque builders){
      +      for (NodeBuilder node : builders) {
      +         if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) {
      +            return;
      +         } else if (node.exists()) {
      +            node.remove();
      +         }
      +      }
      +   }
      +
      +   private void insert(NodeBuilder index, String key, String value) {
      +      //        NodeBuilder builder = index.child(key);
      +      NodeBuilder builder = fetchKeyNode(index, key);
      +      for (String name : PathUtils.elements(value)) {
      +         builder = builder.child(name);
      +      }
      +      builder.setProperty("match", true);
      +   }
      +
      +   /**
      +    * fetch from the index the key node
      +    * 
      +    * @param index the current index root
      +    * @param key the 'key' to fetch from the repo
      +    * @return the node representing the key
      +    */
      +   NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index,@Nonnull String key){
      +      return index.child(key);
      +   }
      +
      +   public Iterable query(final Filter filter, final String indexName,
      +         final NodeState indexMeta, final String indexStorageNodeName,
      +         final Iterable values) {
      +      final NodeState index = indexMeta.getChildNode(indexStorageNodeName);
      +      return new Iterable() {
      +         @Override
      +         public Iterator iterator() {
      +            PathIterator it = new PathIterator(filter, indexName);
      +            if (values == null) {
      +               it.setPathContainsValue(true);
      +               it.enqueue(getChildNodeEntries(index).iterator());
      +            } else {
      +               for (String p : values) {
      +                  NodeState property = index.getChildNode(p);
      +                  if (property.exists()) {
      +                     // we have an entry for this value, so use it
      +                     it.enqueue(Iterators.singletonIterator(
      +                           new MemoryChildNodeEntry("", property)));
      +                  }
      +               }
                   }
      -            return !closed;
      -        }
      -
      -        private void fetchNext() {
      -            while (true) {
      -                fetchNextPossiblyDuplicate();
      -                if (closed) {
      -                    return;
      -                }
      -                if (pathContainsValue) {
      -                    String value = PathUtils.elements(currentPath).iterator().next();
      -                    currentPath = PathUtils.relativize(value, currentPath);
      -                    // don't return duplicate paths:
      -                    // Set.add returns true if the entry was new,
      -                    // so if it returns false, it was already known
      -                    if (!knownPaths.add(currentPath)) {
      -                        continue;
      -                    }
      -                }
      -                break;
      +            return it;
      +         }
      +      };
      +   }
      +
      +   @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index){
      +      return index.getChildNodeEntries();
      +   }
      +
      +   @Override
      +   public Iterable query(final Filter filter, final String indexName, 
      +         final NodeState indexMeta, final Iterable values) {
      +      return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values);
      +   }
      +
      +   @Override
      +   public long count(NodeState indexMeta, Set values, int max) {
      +      return count(indexMeta, INDEX_CONTENT_NODE_NAME, values, max);
      +   }
      +
      +   public long count(NodeState indexMeta, final String indexStorageNodeName,
      +         Set values, int max) {
      +      NodeState index = indexMeta.getChildNode(indexStorageNodeName);
      +      int count = 0;
      +      if (values == null) {
      +         PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME);
      +         if (ec != null) {
      +            return ec.getValue(Type.LONG);
      +         }
      +         CountingNodeVisitor v = new CountingNodeVisitor(max);
      +         v.visit(index);
      +         count = v.getEstimatedCount();
      +         // "is not null" queries typically read more data
      +         count *= 10;
      +      } else {
      +         int size = values.size();
      +         if (size == 0) {
      +            return 0;
      +         }
      +         max = Math.max(10, max / size);
      +         int i = 0;
      +         for (String p : values) {
      +            if (count > max && i > 3) {
      +               // the total count is extrapolated from the the number 
      +               // of values counted so far to the total number of values
      +               count = count * size / i;
      +               break;
                   }
      -        }
      -        
      -        private void fetchNextPossiblyDuplicate() {
      -            while (!nodeIterators.isEmpty()) {
      -                Iterator iterator = nodeIterators.getLast();
      -                if (iterator.hasNext()) {
      -                    ChildNodeEntry entry = iterator.next();
      -
      -                    readCount++;
      -                    if (readCount % 1000 == 0) {
      -                        FilterIterators.checkReadLimit(readCount);
      -                        LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " + filter);
      -                    }
      -
      -                    NodeState node = entry.getNodeState();
      -
      -                    String name = entry.getName();
      -                    if (NodeStateUtils.isHidden(name)) {
      -                        continue;
      -                    }
      -                    currentPath = PathUtils.concat(parentPath, name);
      -
      -                    nodeIterators.addLast(node.getChildNodeEntries().iterator());
      -                    parentPath = currentPath;
      -
      -                    if (node.getBoolean("match")) {
      -                        return;
      -                    }
      -                    
      -                } else {
      -                    nodeIterators.removeLast();
      -                    parentPath = PathUtils.getParentPath(parentPath);
      -                }
      +            NodeState s = index.getChildNode(p);
      +            if (s.exists()) {
      +               CountingNodeVisitor v = new CountingNodeVisitor(max);
      +               v.visit(s);
      +               count += v.getEstimatedCount();
                   }
      -            currentPath = null;
      -            closed = true;
      -        }
      +            i++;
      +         }
      +      }
      +      return count;
      +   }
      +
      +   /**
      +    * An iterator over paths within an index node.
      +    */
      +   static class PathIterator implements Iterator {
       
      -        @Override
      -        public String next() {
      +      private final Filter filter;
      +      private final String indexName;
      +      private final Deque> nodeIterators =
      +            Queues.newArrayDeque();
      +      private int readCount;
      +      private boolean init;
      +      private boolean closed;
      +      private String parentPath;
      +      private String currentPath;
      +      private boolean pathContainsValue;
      +
      +      /**
      +       * Keep the returned path, to avoid returning duplicate entries.
      +       */
      +      private final Set knownPaths = Sets.newHashSet();
      +
      +      PathIterator(Filter filter, String indexName) {
      +         this.filter = filter;
      +         this.indexName = indexName;
      +         parentPath = "";
      +         currentPath = "/";
      +      }
      +
      +      void enqueue(Iterator it) {
      +         nodeIterators.addLast(it);
      +      }
      +
      +      void setPathContainsValue(boolean pathContainsValue) {
      +         if (init) {
      +            throw new IllegalStateException("This iterator is already initialized");
      +         }
      +         this.pathContainsValue = pathContainsValue;
      +      }
      +
      +      @Override
      +      public boolean hasNext() {
      +         if (!closed && !init) {
      +            fetchNext();
      +            init = true;
      +         }
      +         return !closed;
      +      }
      +
      +      private void fetchNext() {
      +         while (true) {
      +            fetchNextPossiblyDuplicate();
                   if (closed) {
      -                throw new IllegalStateException("This iterator is closed");
      +               return;
                   }
      -            if (!init) {
      -                fetchNext();
      -                init = true;
      +            if (pathContainsValue) {
      +               String value = PathUtils.elements(currentPath).iterator().next();
      +               currentPath = PathUtils.relativize(value, currentPath);
      +               // don't return duplicate paths:
      +               // Set.add returns true if the entry was new,
      +               // so if it returns false, it was already known
      +               if (!knownPaths.add(currentPath)) {
      +                  continue;
      +               }
                   }
      -            String result = currentPath;
      -            fetchNext();
      -            return result;
      -        }
      -        
      -        @Override
      -        public void remove() {
      -            throw new UnsupportedOperationException();
      -        }
      -        
      -    }
      -    
      -    /**
      -     * A node visitor to recursively traverse a number of nodes.
      -     */
      -    interface NodeVisitor {
      -        void visit(NodeState state);
      -    }
      -    
      -    /**
      -     * A node visitor that counts the number of matching nodes up to a given
      -     * maximum, in order to estimate the number of matches.
      -     */
      -    static class CountingNodeVisitor implements NodeVisitor {
      -        
      -        /**
      -         * The maximum number of matching nodes to count.
      -         */
      -        final int maxCount;
      -        
      -        /**
      -         * The current count of matching nodes.
      -         */
      -        int count;
      -        
      -        /**
      -         * The current depth (number of parent nodes).
      -         */
      -        int depth;
      -
      -        /**
      -         * The sum of the depth of all matching nodes. This value is used to
      -         * calculate the average depth.
      -         */
      -        long depthTotal;
      -        
      -        CountingNodeVisitor(int maxCount) {
      -            this.maxCount = maxCount;
      -        }
      -
      -        @Override
      -        public void visit(NodeState state) {
      -            if (state.hasProperty("match")) {
      -                count++;
      -                depthTotal += depth;
      +            break;
      +         }
      +      }
      +
      +      private void fetchNextPossiblyDuplicate() {
      +         while (!nodeIterators.isEmpty()) {
      +            Iterator iterator = nodeIterators.getLast();
      +            if (iterator.hasNext()) {
      +               ChildNodeEntry entry = iterator.next();
      +
      +               readCount++;
      +               if (readCount % 1000 == 0) {
      +                  FilterIterators.checkReadLimit(readCount);
      +                  LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " + filter);
      +               }
      +
      +               NodeState node = entry.getNodeState();
      +
      +               String name = entry.getName();
      +               if (NodeStateUtils.isHidden(name)) {
      +                  continue;
      +               }
      +               currentPath = PathUtils.concat(parentPath, name);
      +
      +               nodeIterators.addLast(node.getChildNodeEntries().iterator());
      +               parentPath = currentPath;
      +
      +               if (node.getBoolean("match")) {
      +                  return;
      +               }
      +
      +            } else {
      +               nodeIterators.removeLast();
      +               parentPath = PathUtils.getParentPath(parentPath);
                   }
      -            if (count < maxCount) {
      -                depth++;
      -                for (ChildNodeEntry entry : state.getChildNodeEntries()) {
      -                    if (count >= maxCount) {
      -                        break;
      -                    }
      -                    visit(entry.getNodeState());
      -                }
      -                depth--;
      +         }
      +         currentPath = null;
      +         closed = true;
      +      }
      +
      +      @Override
      +      public String next() {
      +         if (closed) {
      +            throw new IllegalStateException("This iterator is closed");
      +         }
      +         if (!init) {
      +            fetchNext();
      +            init = true;
      +         }
      +         String result = currentPath;
      +         fetchNext();
      +         return result;
      +      }
      +
      +      @Override
      +      public void remove() {
      +         throw new UnsupportedOperationException();
      +      }
      +
      +   }
      +
      +   /**
      +    * A node visitor to recursively traverse a number of nodes.
      +    */
      +   interface NodeVisitor {
      +      void visit(NodeState state);
      +   }
      +
      +   /**
      +    * A node visitor that counts the number of matching nodes up to a given
      +    * maximum, in order to estimate the number of matches.
      +    */
      +   static class CountingNodeVisitor implements NodeVisitor {
      +
      +      /**
      +       * The maximum number of matching nodes to count.
      +       */
      +      final int maxCount;
      +
      +      /**
      +       * The current count of matching nodes.
      +       */
      +      int count;
      +
      +      /**
      +       * The current depth (number of parent nodes).
      +       */
      +      int depth;
      +
      +      /**
      +       * The sum of the depth of all matching nodes. This value is used to
      +       * calculate the average depth.
      +       */
      +      long depthTotal;
      +
      +      CountingNodeVisitor(int maxCount) {
      +         this.maxCount = maxCount;
      +      }
      +
      +      @Override
      +      public void visit(NodeState state) {
      +         if (state.hasProperty("match")) {
      +            count++;
      +            depthTotal += depth;
      +         }
      +         if (count < maxCount) {
      +            depth++;
      +            for (ChildNodeEntry entry : state.getChildNodeEntries()) {
      +               if (count >= maxCount) {
      +                  break;
      +               }
      +               visit(entry.getNodeState());
                   }
      -        }
      -        
      -        /**
      -         * The number of matches (at most the maximum count).
      -         * 
      -         * @return the match count
      -         */
      -        int getCount() {
      +            depth--;
      +         }
      +      }
      +
      +      /**
      +       * The number of matches (at most the maximum count).
      +       * 
      +       * @return the match count
      +       */
      +      int getCount() {
      +         return count;
      +      }
      +
      +      /**
      +       * The number of estimated matches. This value might be higher than the
      +       * number of counted matches, if the maximum number of matches has been
      +       * reached. It is based on the average depth of matches, and the average
      +       * number of child nodes.
      +       * 
      +       * @return the estimated matches
      +       */
      +      int getEstimatedCount() {
      +         if (count < maxCount) {
                   return count;
      -        }
      -        
      -        /**
      -         * The number of estimated matches. This value might be higher than the
      -         * number of counted matches, if the maximum number of matches has been
      -         * reached. It is based on the average depth of matches, and the average
      -         * number of child nodes.
      -         * 
      -         * @return the estimated matches
      -         */
      -        int getEstimatedCount() {
      -            if (count < maxCount) {
      -                return count;
      -            }
      -            double averageDepth = (int) (depthTotal / count);
      -            // the number of estimated matches is higher
      -            // the higher the average depth of the first hits
      -            long estimatedNodes = (long) (count * Math.pow(1.1, averageDepth));
      -            estimatedNodes = Math.min(estimatedNodes, Integer.MAX_VALUE);
      -            return Math.max(count, (int) estimatedNodes);
      -        }
      -        
      -    }
      +         }
      +         double averageDepth = (int) (depthTotal / count);
      +         // the number of estimated matches is higher
      +         // the higher the average depth of the first hits
      +         long estimatedNodes = (long) (count * Math.pow(1.1, averageDepth));
      +         estimatedNodes = Math.min(estimatedNodes, Integer.MAX_VALUE);
      +         return Math.max(count, (int) estimatedNodes);
      +      }
      +
      +   }
       
       }
      diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
      new file mode 100644
      index 0000000..22d7717
      --- /dev/null
      +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java
      @@ -0,0 +1,257 @@
      +/*
      + * 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.strategy;
      +
      +import java.util.Collections;
      +import java.util.Deque;
      +import java.util.Iterator;
      +import java.util.NoSuchElementException;
      +import java.util.Set;
      +
      +import javax.annotation.Nonnull;
      +import javax.annotation.Nullable;
      +
      +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
      +import org.apache.jackrabbit.oak.spi.state.AbstractChildNodeEntry;
      +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
      +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
      +import org.apache.jackrabbit.oak.spi.state.NodeState;
      +import org.slf4j.Logger;
      +import org.slf4j.LoggerFactory;
      +
      +import com.google.common.base.Strings;
      +
      +
      +public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrategy {
      +   private static final Logger log = LoggerFactory.getLogger(OrderedContentMirrorStoreStrategy.class);
      +   
      +   /**
      +    * the property linking to the next node
      +    */
      +   public static final String NEXT = ":next";
      +   
      +   /**
      +    * node that works as root of the index (start point or 0 element)
      +    */
      +   public static final String START = ":start";
      +   
      +   /**
      +    * a NodeState used for easy creating of an empty :start
      +    */
      +   public static final NodeState EMPTY_START_NODE = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState(); 
      +   
      +   @Override
      +   NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) {
      +      log.debug("fetchKeyNode() - index: {} - key: {}",index,key);
      +      NodeBuilder _key = null;
      +      NodeBuilder start = index.child(START);
      +      
      +      //identifying the right place for insert
      +      String n = start.getString(NEXT);
      +      if(Strings.isNullOrEmpty(n)){
      +         //new/empty index
      +         _key = index.child(key);
      +         _key.setProperty(NEXT,"");
      +         start.setProperty(NEXT, key);
      +      }else{
      +         //specific use-case where the item has to be added as first of the list
      +         String nextKey = n;
      +         if(key.compareTo(nextKey)<0){
      +            _key = index.child(key);
      +            _key.setProperty(NEXT,nextKey);
      +            start.setProperty(NEXT, key);
      +         }else{
      +            Iterable children = getChildNodeEntries(index.getNodeState());
      +            for(ChildNodeEntry child : children){
      +               nextKey = child.getNodeState().getString(NEXT);
      +               if(Strings.isNullOrEmpty(nextKey)){
      +                  //we're at the last element, therefore our 'key' has to be appended
      +                  index.getChildNode(child.getName()).setProperty(NEXT, key);
      +                  _key = index.child(key);
      +                  _key.setProperty(NEXT, "");
      +               } else {
      +                  if(key.compareTo(nextKey)<0){
      +                     index.getChildNode(child.getName()).setProperty(NEXT, key);
      +                     _key = index.child(key);
      +                     _key.setProperty(NEXT, nextKey);
      +                     break;
      +                  } 
      +               }
      +            }
      +         }
      +      }
      +      
      +      return _key;
      +   }
      +
      +   @Override
      +   void prune(final NodeBuilder index, final Deque builders){
      +      for(NodeBuilder node : builders){
      +         if(node.hasProperty("match") || node.getChildNodeCount(1) > 0){
      +            return;
      +         }else if (node.exists()) {
      +            if(node.hasProperty(NEXT)){
      +               //it's an index key and we have to relink the list
      +               ChildNodeEntry previous = findPrevious(index.getNodeState(), node.getNodeState()); //(1) find the previous element
      +               log.debug("previous: {}",previous);
      +               String next = node.getString(NEXT); //(2) find the next element
      +               if(next==null) {
      +                   next = "";
      +               }
      +               index.getChildNode(previous.getName()).setProperty(NEXT, next); //(3) re-link the previous to the next
      +               node.remove(); //(4) remove the current node
      +            }else{
      +               node.remove();
      +            }
      +         }
      +      }
      +   }
      +
      +   @Nullable ChildNodeEntry findPrevious(@Nonnull final NodeState index, @Nonnull final NodeState node){
      +      ChildNodeEntry previous = null;
      +      ChildNodeEntry current = null; 
      +      boolean found = false;
      +      Iterator it = getChildNodeEntries(index,true).iterator();
      +      
      +      while(!found && it.hasNext()){
      +         current = it.next();
      +         if(previous==null){
      +            //first iteration
      +            previous = current;
      +         }else{
      +            found = node.equals(current.getNodeState());
      +            if(!found) {
      +                previous = current;
      +            }
      +         }
      +      }
      +      
      +      return ((found) ? previous : null);
      +   }
      +   
      +   @Override
      +   public void update(NodeBuilder index, String path, Set beforeKeys, Set afterKeys) {
      +      log.debug("update() - index     : {}",index);
      +      log.debug("update() - path      : {}",path);
      +      log.debug("update() - beforeKeys: {}",beforeKeys);
      +      log.debug("update() - afterKeys : {}",afterKeys);
      +      super.update(index, path, beforeKeys, afterKeys);
      +   }
      +   
      +   
      +   /**
      +    * retrieve an Iterable for going through the index in the right order without the :start node
      +    * 
      +    * @param index the root of the index (:index)
      +    * @return
      +    */
      +   @Override
      +   @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index){
      +      return getChildNodeEntries(index, false);
      +   }
      +   
      +   /**
      +    * Retrieve an Iterable for going through the index in the right order with potentially the :start node
      +    * 
      +    * @param index the root of the index (:index)
      +    * @param includeStart true if :start should be included as first element
      +    * @return
      +    */
      +   @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index, final boolean includeStart){
      +      Iterable cne = null;
      +      final NodeState start = index.getChildNode(START);
      +      
      +      if((!start.exists() || Strings.isNullOrEmpty(start.getString(NEXT))) && !includeStart) {
      +         //if the property is not there or is empty it means we're empty
      +         cne = Collections.emptyList();
      +      }else{
      +         cne = new Iterable(){
      +            private NodeState _index = index;
      +            private NodeState _start = ((includeStart && !start.exists())?EMPTY_START_NODE:start);
      +            private NodeState current = _start;
      +            private boolean _includeStart = includeStart;
      +
      +            @Override
      +            public Iterator iterator() {
      +               return new Iterator(){
      +
      +                  @Override
      +                  public boolean hasNext() {
      +                     return (
      +                              (_includeStart && _start.equals(current)) || 
      +                              (!_includeStart && !Strings.isNullOrEmpty(current.getString(NEXT)))
      +                     );
      +                  }
      +
      +                  @Override
      +                  public ChildNodeEntry next() {
      +                     ChildNodeEntry _cne = null;
      +                     if(_includeStart && _start.equals(current)){
      +                        _cne = new OrderedChildNodeEntry(START, current);
      +                        _includeStart = false; //let's set it to false. We just included it.
      +                     } else {
      +                        if(hasNext()){
      +                           final String name = current.getString(NEXT);
      +                           current = _index.getChildNode(name);
      +                           _cne = new OrderedChildNodeEntry(name, current);
      +                        }else{
      +                           throw new NoSuchElementException();
      +                        }
      +                     }
      +                     return _cne;
      +                  }
      +
      +                  @Override
      +                  public void remove() {
      +                     throw new UnsupportedOperationException();
      +                  }
      +               };
      +            }
      +         }; 
      +      }
      +      return cne;   
      +   }
      +   
      +   private static final class OrderedChildNodeEntry extends AbstractChildNodeEntry {
      +      private final String name;
      +      private final NodeState state;
      +      
      +      public OrderedChildNodeEntry(@Nonnull final String name,@Nonnull final NodeState state){
      +         this.name = name;
      +         this.state = state;
      +      }
      +      
      +      /* (non-Javadoc)
      +       * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getName()
      +       */
      +      @Override
      +      @Nonnull
      +      public String getName() {
      +         return name;
      +      }
      +
      +      /* (non-Javadoc)
      +       * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getNodeState()
      +       */
      +      @Override
      +      @Nonnull
      +      public NodeState getNodeState() {
      +         return state;
      +      }
      +   }
      +}
      diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java
      new file mode 100644
      index 0000000..34f4562
      --- /dev/null
      +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java
      @@ -0,0 +1,67 @@
      +package org.apache.jackrabbit.oak.plugins.index.property;
      +
      +import static org.easymock.EasyMock.createNiceMock;
      +import static org.easymock.EasyMock.expect;
      +import static org.easymock.EasyMock.replay;
      +import static org.junit.Assert.assertEquals;
      +import static org.junit.Assert.assertFalse;
      +import static org.junit.Assert.assertNotNull;
      +import static org.junit.Assert.assertTrue;
      +
      +import java.util.Arrays;
      +
      +import org.apache.jackrabbit.oak.api.PropertyState;
      +import org.apache.jackrabbit.oak.api.Type;
      +import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
      +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
      +import org.junit.Test;
      +
      +public class OrderedPropertyIndexEditorTest {
      +   
      +   @Test public void isProperlyConfiguredWithPropertyNames(){
      +      NodeBuilder definition = createNiceMock(NodeBuilder.class);
      +      PropertyState names = createNiceMock(PropertyState.class);
      +      expect(names.count()).andReturn(1);
      +      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
      +      replay(names);
      +      replay(definition);
      +      
      +      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
      +      assertFalse("With empty or missing property the index should not work.",ie.isProperlyConfigured());
      +   }
      +   
      +   @Test public void isProperlyConfiguredSingleValuePropertyNames(){
      +      NodeBuilder definition = createNiceMock(NodeBuilder.class);
      +      PropertyState names = createNiceMock(PropertyState.class);
      +      expect(names.count()).andReturn(1);
      +      expect(names.getValue(Type.NAME,0)).andReturn("jcr:lastModified").anyTimes();
      +      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
      +      replay(names);
      +      replay(definition);
      +      
      +      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
      +      assertNotNull("With a correct property set 'propertyNames' can't be null",ie.getPropertyNames());
      +      assertEquals(1,ie.getPropertyNames().size());
      +      assertEquals("jcr:lastModified",ie.getPropertyNames().iterator().next());
      +      assertTrue("Expecting a properly configured index",ie.isProperlyConfigured());
      +   }
      +   
      +   @Test public void multiValueProperty(){
      +      NodeBuilder definition = createNiceMock(NodeBuilder.class);
      +      PropertyState names = createNiceMock(PropertyState.class);
      +      expect(names.isArray()).andReturn(true).anyTimes();
      +      expect(names.count()).andReturn(2).anyTimes();
      +      expect(names.getValue(Type.NAME,0)).andReturn("jcr:lastModified").anyTimes();
      +      expect(names.getValue(Type.NAME,1)).andReturn("foo:bar").anyTimes();
      +      expect(names.getValue(Type.NAMES)).andReturn(Arrays.asList("jcr:lastModified","foo:bar")).anyTimes();
      +      expect(definition.getProperty(IndexConstants.PROPERTY_NAMES)).andReturn(names).anyTimes();
      +      replay(names);
      +      replay(definition);
      +
      +      OrderedPropertyIndexEditor ie = new OrderedPropertyIndexEditor(definition, null, null);
      +      assertNotNull("With a correct property set 'propertyNames' can't be null",ie.getPropertyNames());
      +      assertEquals("When multiple properties are a passed only the first one is taken", 1,ie.getPropertyNames().size());
      +      assertEquals("jcr:lastModified",ie.getPropertyNames().iterator().next());
      +      assertTrue("Expecting a properly configured index",ie.isProperlyConfigured());
      +   }   
      +}
      diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java
      new file mode 100644
      index 0000000..5329c4c
      --- /dev/null
      +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexQueryTest.java
      @@ -0,0 +1,340 @@
      +/*
      + * 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 junit.framework.Assert.assertEquals;
      +import static junit.framework.Assert.assertFalse;
      +import static junit.framework.Assert.assertTrue;
      +import static junit.framework.Assert.fail;
      +import static org.apache.jackrabbit.JcrConstants.JCR_PRIMARYTYPE;
      +import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED;
      +
      +import java.text.DecimalFormat;
      +import java.text.NumberFormat;
      +import java.text.ParseException;
      +import java.util.ArrayList;
      +import java.util.Collections;
      +import java.util.Iterator;
      +import java.util.List;
      +import java.util.Map;
      +import java.util.Random;
      +
      +import javax.annotation.Nonnull;
      +import javax.jcr.RepositoryException;
      +
      +import org.apache.jackrabbit.oak.Oak;
      +import org.apache.jackrabbit.oak.api.CommitFailedException;
      +import org.apache.jackrabbit.oak.api.ContentRepository;
      +import org.apache.jackrabbit.oak.api.PropertyValue;
      +import org.apache.jackrabbit.oak.api.ResultRow;
      +import org.apache.jackrabbit.oak.api.Tree;
      +import org.apache.jackrabbit.oak.api.Type;
      +import org.apache.jackrabbit.oak.plugins.index.IndexConstants;
      +import org.apache.jackrabbit.oak.plugins.index.IndexUtils;
      +import org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent;
      +import org.apache.jackrabbit.oak.query.AbstractQueryTest;
      +import org.apache.jackrabbit.oak.spi.query.PropertyValues;
      +import org.apache.jackrabbit.oak.spi.security.OpenSecurityProvider;
      +import org.apache.jackrabbit.oak.util.NodeUtil;
      +import org.junit.Test;
      +
      +import com.google.common.collect.ImmutableMap;
      +
      +public class OrderedPropertyIndexQueryTest extends AbstractQueryTest {
      +    /**
      +     * the property used by the index
      +     */
      +    public static final String ORDERED_PROPERTY = "foo";
      +
      +    /**
      +     * number of nodes to create for testing.
      +     * 
      +     * It has been found during development that in some cases the order of the nodes creation within the persistence
      +     * where the actual expected order.
      +     * 
      +     * The higher the value the lower the chance for this to happen.
      +     */
      +    private static final int NUMBER_OF_NODES = 50;
      +
      +    /**
      +     * convenience orderable object that represents a tuple of values and paths
      +     * 
      +     * where the values are the indexed keys from the index and the paths are the path which hold the key
      +     */
      +    private class ValuePathTuple implements Comparable {
      +        private final String value;
      +        private final String path;
      +
      +        ValuePathTuple(String value, String path) {
      +            this.value = value;
      +            this.path = path;
      +        }
      +
      +        @Override
      +        public int hashCode() {
      +            final int prime = 31;
      +            int result = 1;
      +            result = prime * result + getOuterType().hashCode();
      +            result = prime * result + ((path == null) ? 0 : path.hashCode());
      +            result = prime * result + ((value == null) ? 0 : value.hashCode());
      +            return result;
      +        }
      +
      +        @Override
      +        public boolean equals(Object obj) {
      +            if (this == obj){
      +                return true;
      +            }
      +            if (obj == null){
      +                return false;
      +            }
      +            if (getClass() != obj.getClass()){
      +                return false;
      +            }
      +            ValuePathTuple other = (ValuePathTuple) obj;
      +            if (!getOuterType().equals(other.getOuterType())){
      +                return false;
      +            }
      +            if (path == null) {
      +                if (other.path != null){
      +                    return false;
      +                }
      +            } else if (!path.equals(other.path)){
      +                return false;
      +            }
      +            if (value == null) {
      +                if (other.value != null){
      +                    return false;
      +                }
      +            } else if (!value.equals(other.value)){
      +                return false;
      +            }
      +            return true;
      +        }
      +
      +        @Override
      +        public int compareTo(ValuePathTuple o) {
      +            if (this.equals(o)){
      +                return 0;
      +            }
      +            if (this.value.compareTo(o.value) < 0){
      +                return -1;
      +            }
      +            if (this.value.compareTo(o.value) > 0){
      +                return 1;
      +            }
      +            if (this.path.compareTo(o.path) < 0){
      +                return -1;
      +            }
      +            if (this.path.compareTo(o.path) > 0){
      +                return 1;
      +            }
      +            return 0;
      +        }
      +
      +        private OrderedPropertyIndexQueryTest getOuterType() {
      +            return OrderedPropertyIndexQueryTest.this;
      +        }
      +
      +    }
      +
      +    /**
      +     * testing for asserting the right comparison behaviour of the custom class
      +     */
      +    @Test
      +    public void valuePathTupleComparison() {
      +        try {
      +            new ValuePathTuple("value", "path").compareTo(null);
      +            fail("It should have raised a NPE");
      +        } catch (NullPointerException e) {
      +            // so far so good
      +        }
      +        assertEquals(0, (new ValuePathTuple("value", "path")).compareTo(new ValuePathTuple("value", "path")));
      +        assertEquals(-1, (new ValuePathTuple("value", "path")).compareTo(new ValuePathTuple("value1", "path")));
      +        assertEquals(-1, (new ValuePathTuple("value1", "path")).compareTo(new ValuePathTuple("value1", "path1")));
      +        assertEquals(1, (new ValuePathTuple("value1", "path")).compareTo(new ValuePathTuple("value", "path")));
      +        assertEquals(1, (new ValuePathTuple("value1", "path1")).compareTo(new ValuePathTuple("value1", "path")));
      +
      +        assertEquals(-1,
      +            (new ValuePathTuple("value000", "/test/n1")).compareTo(new ValuePathTuple("value001", "/test/n0")));
      +        assertEquals(1,
      +            (new ValuePathTuple("value001", "/test/n0")).compareTo(new ValuePathTuple("value000", "/test/n1")));
      +    }
      +
      +    @Override
      +    protected ContentRepository createRepository() {
      +        return new Oak().with(new InitialContent())
      +            .with(new OpenSecurityProvider())
      +            // .with(new PropertyIndexProvider())
      +            // .with(new PropertyIndexEditorProvider())
      +            .with(new OrderedPropertyIndexProvider()).with(new OrderedPropertyIndexEditorProvider())
      +            .createContentRepository();
      +    }
      +
      +    /**
      +     * create a child node for the provided father
      +     * 
      +     * @param father
      +     * @param name
      +     *            the name of the node to create
      +     * @param propName
      +     *            the name of the property to assign
      +     * @param propValue
      +     *            the value of the property to assign
      +     * @return
      +     */
      +    private static Tree child(Tree father, String name, String propName, String propValue) {
      +        Tree child = father.addChild(name);
      +        child.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, Type.NAME);
      +        child.setProperty(propName, propValue, Type.STRING);
      +        return child;
      +    }
      +
      +    /**
      +     * generate a list of values to be used as ordered set. Will return something like
      +     * {@code value000, value001, value002, ...}
      +     * 
      +     * 
      +     * @param amount
      +     * @return
      +     */
      +    private static List generateOrderedValues(int amount) {
      +        if (amount > 1000){
      +            throw new RuntimeException("amount cannot be greater than 100");
      +        }
      +        List values = new ArrayList(amount);
      +        NumberFormat nf = new DecimalFormat("000");
      +        for (int i = 0; i < amount; i++){
      +            values.add(String.format("value%s", String.valueOf(nf.format(i))));
      +        }
      +        return values;
      +    }
      +
      +    /**
      +     * convenience method that adds a bunch of nodes in random order and return the order in which they should be
      +     * presented by the OrderedIndex
      +     * 
      +     * @param values
      +     *            the values of the property that will be indexed
      +     * @param father
      +     *            the father under which add the nodes
      +     * @return
      +     */
      +    private List addChildNodes(final List values, final Tree father) {
      +        List nodes = new ArrayList();
      +        Random rnd = new Random();
      +        int counter = 0;
      +        while (!values.isEmpty()) {
      +            String v = values.remove(rnd.nextInt(values.size()));
      +            Tree t = child(father, String.format("n%s", counter++), ORDERED_PROPERTY, v);
      +            nodes.add(new ValuePathTuple(v, t.getPath()));
      +        }
      +
      +        Collections.sort(nodes);
      +        return nodes;
      +    }
      +
      +    @Override
      +    protected void createTestIndexNode() throws Exception {
      +        Tree index = root.getTree("/");
      +        IndexUtils.createIndexDefinition(new NodeUtil(index.getChild(IndexConstants.INDEX_DEFINITIONS_NAME)),
      +            TEST_INDEX_NAME, false, new String[] { ORDERED_PROPERTY }, null, OrderedIndex.TYPE);
      +        root.commit();
      +    }
      +
      +    /**
      +     * assert the right order of the returned resultset
      +     * 
      +     * @param orderedSequence
      +     *            the right order in which the resultset should be returned
      +     * @param resultset
      +     *            the resultset
      +     */
      +    private void assertRightOrder(@Nonnull
      +    final List orderedSequence, @Nonnull
      +    final Iterator resultset) {
      +        assertTrue("No results returned", resultset.hasNext());
      +        int counter = 0;
      +        while (resultset.hasNext() && counter < orderedSequence.size()) {
      +            ResultRow row = resultset.next();
      +            assertEquals(String.format("Wrong path at the element '%d'", counter), orderedSequence.get(counter).path,
      +                row.getPath());
      +            counter++;
      +        }
      +    }
      +
      +    /**
      +     * Query the index for retrieving all the entries
      +     * 
      +     * @throws CommitFailedException
      +     * @throws ParseException
      +     * @throws RepositoryException
      +     */
      +    @Test
      +    public void queryAllEntries() throws CommitFailedException, ParseException, RepositoryException {
      +        setTravesalEnabled(false);
      +
      +        // index automatically created by the framework:
      +        // {@code createTestIndexNode()}
      +
      +        Tree rTree = root.getTree("/");
      +        Tree test = rTree.addChild("test");
      +        List nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test);
      +        root.commit();
      +
      +        // querying
      +        Iterator results;
      +        results = executeQuery(String.format("SELECT * from [%s] WHERE foo IS NOT NULL", NT_UNSTRUCTURED), SQL2, null)
      +            .getRows().iterator();
      +        assertRightOrder(nodes, results);
      +
      +        setTravesalEnabled(true);
      +    }
      +
      +    /**
      +     * test the index for returning the items related to a single key
      +     * 
      +     * @throws CommitFailedException
      +     * @throws ParseException
      +     */
      +    @Test
      +    public void queryOneKey() throws CommitFailedException, ParseException {
      +        setTravesalEnabled(false);
      +
      +        // index automatically created by the framework:
      +        // {@code createTestIndexNode()}
      +
      +        Tree rTree = root.getTree("/");
      +        Tree test = rTree.addChild("test");
      +        List nodes = addChildNodes(generateOrderedValues(NUMBER_OF_NODES), test);
      +        root.commit();
      +
      +        ValuePathTuple searchfor = nodes.get(NUMBER_OF_NODES / 2); // getting the middle of the random list of
      +                                                                         // nodes.
      +        Map filter = ImmutableMap
      +            .of(ORDERED_PROPERTY, PropertyValues.newString(searchfor.value));
      +        String query = "SELECT * FROM [%s] WHERE %s=$%s";
      +        Iterator results = executeQuery(
      +            String.format(query, NT_UNSTRUCTURED, ORDERED_PROPERTY, ORDERED_PROPERTY), SQL2, filter).getRows()
      +            .iterator();
      +        assertTrue("one element is expected", results.hasNext());
      +        assertEquals("wrong path returned", searchfor.path, results.next().getPath());
      +        assertFalse("there should be not any more items", results.hasNext());
      +
      +        setTravesalEnabled(true);
      +    }
      +}
      diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
      new file mode 100644
      index 0000000..527edcc
      --- /dev/null
      +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java
      @@ -0,0 +1,990 @@
      +/*
      + * 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.strategy;
      +
      +import static com.google.common.collect.Sets.newHashSet;
      +import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.NEXT;
      +import static org.apache.jackrabbit.oak.plugins.index.property.strategy.OrderedContentMirrorStoreStrategy.START;
      +import static org.junit.Assert.assertEquals;
      +import static org.junit.Assert.assertFalse;
      +import static org.junit.Assert.assertNotNull;
      +import static org.junit.Assert.assertTrue;
      +
      +import java.util.Iterator;
      +import java.util.Set;
      +
      +import org.apache.jackrabbit.oak.commons.PathUtils;
      +import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState;
      +import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
      +import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
      +import org.apache.jackrabbit.oak.spi.state.NodeState;
      +import org.junit.Test;
      +
      +import com.google.common.base.Strings;
      +import com.google.common.collect.Iterables;
      +import com.google.common.collect.Iterators;
      +
      +/**
      + *
      + */
      +public class OrderedContentMirrorStorageStrategyTest {
      +    /**
      +     * ascending ordered set of keys. Useful for testing
      +     */
      +    private static final String[] KEYS = new String[] { "donald", "goofy", "mickey", "minnie" };
      +    private static final Set EMPTY_KEY_SET = newHashSet();
      +
      +    /**
      +     * checks that the fist item/key is inserted with an empty property 'next'
      +     * 
      +     * expected structure:
      +     * 
      +     * 
      +     * :index : {
      +     *    :start : { :next=n0 },
      +     *    n0 : { 
      +     *       :next=,
      +     *       foo : {
      +     *          bar: { match=true}
      +     *       }
      +     *    }
      +     * }
      +     * 
      +     */
      +    @Test
      +    public void firstAndOnlyItem() {
      +        final String PATH = "/foo/bar";
      +        final String[] PATH_NODES = Iterables.toArray(PathUtils.elements(PATH), String.class);
      +        final String N0 = KEYS[0];
      +
      +        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        NodeBuilder node = null;
      +
      +        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0));
      +
      +        assertFalse(":index should be left alone with not changes", index.hasProperty(NEXT));
      +        node = index.getChildNode(START);
      +        assertTrue(":index should have the :start node", node.exists());
      +        assertEquals(":start should point to n0", N0, node.getString(NEXT));
      +
      +        node = index.getChildNode(N0);
      +        assertTrue("n0 should exists in the index", node.exists());
      +        assertEquals("n0 should point nowhere as it's the last (and only) element", "", node.getString(NEXT));
      +
      +        // checking content structure below n0
      +        node = node.getChildNode(PATH_NODES[0]);
      +        assertTrue("n0 should contain 'foo'", node.exists());
      +        node = node.getChildNode(PATH_NODES[1]);
      +        assertTrue("'foo' should contain 'bar'", node.exists());
      +        assertTrue("the 'foo' node should have 'match=true'", node.getBoolean("match"));
      +    }
      +
      +    /**
      +     * test the saving of 2 new keys that comes already ordered
      +     * 
      +     * final state of the index will be
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next=n0 },
      +     *       n0 : { :next=n1 },
      +     *       n1 : { :next= }       
      +     *    }
      +     * 
      +     */
      +    @Test
      +    public void first2newKeysAlreadyOrdered() {
      +        final String PATH = "/foo/bar";
      +        final String N0 = KEYS[0];
      +        final String N1 = KEYS[1];
      +
      +        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        NodeBuilder node = null;
      +
      +        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0)); // first node
      +                                                                  // arrives
      +
      +        node = index.getChildNode(START);
      +        assertTrue(":index should have :start", node.exists());
      +        assertEquals(":start should point to n0", N0, node.getString(NEXT));
      +
      +        node = index.getChildNode(N0);
      +        assertTrue(":index should have n0", node.exists());
      +        assertEquals("n0 should point nowhere at this stage", "", node.getString(NEXT));
      +
      +        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N1)); // second node
      +                                                                  // arrives
      +
      +        node = index.getChildNode(START);
      +        assertTrue(":index should still have :start", node.exists());
      +        assertEquals(":start should still point to n0", N0, node.getString(NEXT));
      +
      +        node = index.getChildNode(N0);
      +        assertTrue("n0 should still exists", node.exists());
      +        assertEquals("n0 should point to n1", N1, node.getString(NEXT));
      +
      +        node = index.getChildNode(N1);
      +        assertTrue("n1 should exists", node.exists());
      +        assertEquals("n1 should point nowhere", "", node.getString(NEXT));
      +    }
      +
      +    /**
      +     * Test the iteration of an empty index
      +     */
      +    @Test
      +    public void childNodeEntriesEmptyIndex() {
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeState index = EmptyNodeState.EMPTY_NODE;
      +
      +        @SuppressWarnings("unchecked")
      +        Iterable children = (Iterable) store.getChildNodeEntries(index);
      +
      +        assertNotNull("A returned Iterable cannot be null", children);
      +    }
      +
      +    /**
      +     * test the iteration of the index with 2 shuffled items
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next=n1 },
      +     *       n0 : { :next= },
      +     *       n1 : { :next=n0 }
      +     *    }
      +     * 
      +     */
      +    @SuppressWarnings("unchecked")
      +    @Test
      +    public void childNodeEntriesACoupleOfMixedItems() {
      +        final String N0 = KEYS[1];
      +        final String N1 = KEYS[0];
      +        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
      +        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +
      +        // setting-up the index structure
      +        index.child(START).setProperty(NEXT, N1);
      +        index.setChildNode(N0, NODE_0);
      +        index.setChildNode(N1, NODE_1);
      +
      +        NodeState indexState = index.getNodeState();
      +
      +        Iterable children = (Iterable) store.getChildNodeEntries(indexState);
      +        assertNotNull("The iterable cannot be null", children);
      +        assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // how
      +                                                                                                // many
      +                                                                                                // entries
      +                                                                                                // do
      +                                                                                                // we
      +                                                                                                // have?
      +
      +        // ensuring the right sequence
      +        ChildNodeEntry entry = null;
      +        children = (Iterable) store.getChildNodeEntries(indexState);
      +        Iterator it = children.iterator();
      +        assertTrue("We should have 2 elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The first element should be n1", N1, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
      +        assertTrue("We should have 1 elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The second element should be n0", N0, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
      +        assertFalse("We should have be at the end of the list", it.hasNext());
      +    }
      +
      +    /**
      +     * test the iteration of the index with 2 shuffled items without the :start
      +     * node
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next=n1 },
      +     *       n0 : { :next= },
      +     *       n1 : { :next=n0 }
      +     *    }
      +     * 
      +     */
      +    @SuppressWarnings("unchecked")
      +    @Test
      +    public void childNodeEntriesACoupleOfMixedItemsNoStart() {
      +        final String N0 = KEYS[1];
      +        final String N1 = KEYS[0];
      +        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
      +        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +
      +        // setting-up the index structure
      +        index.child(START).setProperty(NEXT, N1);
      +        index.setChildNode(N0, NODE_0);
      +        index.setChildNode(N1, NODE_1);
      +
      +        NodeState indexState = index.getNodeState();
      +
      +        Iterable children = (Iterable) store.getChildNodeEntries(indexState, false);
      +        assertNotNull("The iterable cannot be null", children);
      +        assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // how
      +                                                                                                // many
      +                                                                                                // entries
      +                                                                                                // do
      +                                                                                                // we
      +                                                                                                // have?
      +
      +        // ensuring the right sequence
      +        ChildNodeEntry entry = null;
      +        children = (Iterable) store.getChildNodeEntries(indexState);
      +        Iterator it = children.iterator();
      +        assertTrue("We should have 2 elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The first element should be n1", N1, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
      +        assertTrue("We should have 1 elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The second element should be n0", N0, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
      +        assertFalse("We should have be at the end of the list", it.hasNext());
      +    }
      +
      +    /**
      +     * test the iteration of the index with 2 shuffled items including the
      +     * :start node as first
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next=n1 },
      +     *       n0 : { :next= },
      +     *       n1 : { :next=n0 }
      +     *    }
      +     * 
      +     */
      +    @SuppressWarnings("unchecked")
      +    @Test
      +    public void childNodeEntriesACoupleOfMixedItemsWithStart() {
      +        final String N0 = KEYS[1];
      +        final String N1 = KEYS[0];
      +        final NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N1).getNodeState();
      +        final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
      +        final NodeState NODE_1 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState();
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +
      +        // setting-up the index structure
      +        index.setChildNode(START, NODE_START);
      +        index.setChildNode(N0, NODE_0);
      +        index.setChildNode(N1, NODE_1);
      +
      +        NodeState indexState = index.getNodeState();
      +
      +        Iterable children = (Iterable) store.getChildNodeEntries(indexState, true);
      +        assertNotNull("The iterable cannot be null", children);
      +        assertEquals("Expecting 3 items in the index", 3, Iterators.size(children.iterator())); // how
      +                                                                                                // many
      +                                                                                                // entries
      +                                                                                                // do
      +                                                                                                // we
      +                                                                                                // have?
      +
      +        // ensuring the right sequence
      +        ChildNodeEntry entry = null;
      +        children = (Iterable) store.getChildNodeEntries(indexState, true);
      +        Iterator it = children.iterator();
      +        assertTrue("We should still have elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The first element should be :start", START, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_START, entry.getNodeState());
      +        assertTrue("We should still have elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The second element should be n1", N1, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_1, entry.getNodeState());
      +        assertTrue("We should still have elements left to loop through", it.hasNext());
      +        entry = it.next();
      +        assertEquals("The third element should be n0", N0, entry.getName());
      +        assertEquals("Wrong entry returned", NODE_0, entry.getNodeState());
      +        assertFalse("We should be at the end of the list", it.hasNext());
      +    }
      +
      +    /**
      +     * test the iteration over an empty list when the :start is required. In
      +     * this case :start should always be returned
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next= }
      +     *    }
      +     * 
      +     */
      +    @Test
      +    public void childNodeEntriesNoItemsWithStart() {
      +        NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +
      +        // setting-up the index
      +        index.setChildNode(START, NODE_START);
      +
      +        Iterable children = store.getChildNodeEntries(index.getNodeState(), true);
      +        assertEquals("Wrong size of Iterable", 1, Iterators.size(children.iterator()));
      +
      +        Iterator it = store.getChildNodeEntries(index.getNodeState(), true).iterator();
      +        assertTrue("We should have at least 1 element", it.hasNext());
      +        ChildNodeEntry entry = it.next();
      +        assertEquals(":start is expected", START, entry.getName());
      +        assertEquals("wrong node returned", NODE_START, entry.getNodeState());
      +        assertFalse("We should be at the end of the list", it.hasNext());
      +    }
      +
      +    /**
      +     * test the case where we want an iterator for the children of a brand new
      +     * index. In this case :start doesn't exists but if we ask for it we should
      +     * return it.
      +     */
      +    @Test
      +    public void childNodeEntriesNewIndexWithStart() {
      +        NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +
      +        Iterator children = store.getChildNodeEntries(index.getNodeState(), true).iterator();
      +        assertEquals("Wrong number of children", 1, Iterators.size(children));
      +
      +        children = store.getChildNodeEntries(index.getNodeState(), true).iterator();
      +        assertTrue("at least one item expected", children.hasNext());
      +        ChildNodeEntry child = children.next();
      +        assertEquals(START, child.getName());
      +        assertEquals(NODE_START, child.getNodeState());
      +        assertFalse(children.hasNext());
      +    }
      +
      +    /**
      +     * test the insert of two shuffled items
      +     * 
      +     * Building final a structure like
      +     * 
      +     * 
      +     *    :index : {
      +     *       :start : { :next=n1 },
      +     *       n0 : { :next= },
      +     *       n1 : { :next=n0 }
      +     *    }
      +     * 
      +     * 
      +     * where:
      +     * 
      +     * 
      +     *    Stage 1
      +     *    =======
      +     * 
      +     *    :index : {
      +     *       :start : { :next = n0 },
      +     *       n0 : {
      +     *          :next = 
      +     *       }
      +     *    }
      +     * 
      +     *    Stage 2
      +     *    =======
      +     * 
      +     *    :index : {
      +     *       :start : { :next = n1 },
      +     *       n0 : {
      +     *          :next =
      +     *       },
      +     *       n1 : {
      +     *          :next = n0
      +     *       }
      +     *    }
      +     * 
      +     */
      +    @Test
      +    public void twoShuffledItems() {
      +        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeState root = EmptyNodeState.EMPTY_NODE;
      +        NodeBuilder index = root.builder();
      +        String key1st = KEYS[1];
      +        String key2nd = KEYS[0];
      +        NodeState ns = null;
      +
      +        // Stage 1
      +        store.update(index, "/foo/bar", EMPTY_KEY_SET, newHashSet(key1st));
      +        ns = index.getChildNode(START).getNodeState();
      +        assertEquals(":start is expected to point to the 1st node", key1st, ns.getString(NEXT));
      +        ns = index.getChildNode(key1st).getNodeState();
      +        assertTrue("At Stage 1 the first node is expected to point nowhere as it's the last",
      +                        Strings.isNullOrEmpty(ns.getString(NEXT)));
      +
      +        // Stage 2
      +        store.update(index, "/foo/bar", EMPTY_KEY_SET, newHashSet(key2nd));
      +        ns = index.getChildNode(START).getNodeState();
      +        assertEquals(":start is expected to point to the 2nd node", key2nd, ns.getString(NEXT));
      +        ns = index.getChildNode(key1st).getNodeState();
      +        assertTrue("At stage 2 the first element should point nowhere as it's the last",
      +                        Strings.isNullOrEmpty(ns.getString(NEXT)));
      +        ns = index.getChildNode(key2nd).getNodeState();
      +        assertEquals("At Stage 2 the second element should point to the first one", key1st, ns.getString(NEXT));
      +    }
      +
      +    /**
      +     * test the insert of shuffled items
      +     * 
      +     * Building a final structure like
      +     * 
      +     * 
      +     *    {
      +     *       :start : { :next = n1 },
      +     *       n0 : {
      +     *          :next = ""
      +     *       },
      +     *       n1 : {
      +     *          :next = n2
      +     *       },
      +     *       n2 : {
      +     *          :next = n0
      +     *       }
      +     *    }
      +     * 
      +     * 
      +     * where:
      +     * 
      +     * 
      +     *    Stage 1
      +     *    =======
      +     * 
      +     *    {
      +     *       :start : { :next = n0 },
      +     *       n0 : {
      +     *          :next = 
      +     *       }
      +     *    }
      +     * 
      +     *    Stage 2
      +     *    =======
      +     * 
      +     *    {
      +     *       :start : { :next = n1 },
      +     *       n0 : { :next = },
      +     *       n1 : { :next = n0 }
      +     *    }
      +     *    
      +     *    Stage 3
      +     *    =======
      +     *    
      +     *    {
      +     *       :start : { :next = n1 },
      +     *       n0 : { :next = },
      +     *       n1 : { :next = n2 },
      +     *       n2 : { :next = n0 }
      +     *    }
      +     *    
      +     *    Stage 4
      +     *    =======
      +     * 
      +     *    {
      +     *       :start : { :next = n1 },
      +     *       n0 : { :next = n3 },
      +     *       n1 : { :next = n2 },
      +     *       n2 : { :next = n0 },
      +     *       n3 : { :next = }
      +     *    }
      +     * 
      +     */
      +    @Test
      +    public void fourShuffledElements() {
      +        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        String n0 = KEYS[2];
      +        String n1 = KEYS[0];
      +        String n2 = KEYS[1];
      +        String n3 = KEYS[3];
      +
      +        // Stage 1
      +        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n0));
      +        assertEquals(":start should point to the first node", n0, index.getChildNode(START).getString(NEXT));
      +        assertTrue("the first node should point nowhere", Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
      +
      +        // Stage 2
      +        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n1));
      +        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
      +        assertEquals("'n1' should point to 'n0'", n0, index.getChildNode(n1).getString(NEXT));
      +        assertTrue("n0 should still be point nowhere", Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
      +
      +        // Stage 3
      +        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n2));
      +        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
      +        assertEquals("n1 should be pointing to n2", n2, index.getChildNode(n1).getString(NEXT));
      +        assertEquals("n2 should be pointing to n0", n0, index.getChildNode(n2).getString(NEXT));
      +        assertTrue("n0 should still be the last item of the list",
      +                        Strings.isNullOrEmpty(index.getChildNode(n0).getString(NEXT)));
      +
      +        // Stage 4
      +        store.update(index, "/a/b", EMPTY_KEY_SET, newHashSet(n3));
      +        assertEquals(":start should point to n1", n1, index.getChildNode(START).getString(NEXT));
      +        assertEquals("n1 should be pointing to n2", n2, index.getChildNode(n1).getString(NEXT));
      +        assertEquals("n2 should be pointing to n0", n0, index.getChildNode(n2).getString(NEXT));
      +        assertEquals("n0 should be pointing to n3", n3, index.getChildNode(n0).getString(NEXT));
      +        assertTrue("n3 should be the last element", Strings.isNullOrEmpty(index.getChildNode(n3).getString(NEXT)));
      +    }
      +
      +    /**
      +     * perform a test where the index gets updated if an already existent
      +     * node/key gets updated by changing the key and the key contains only 1
      +     * item.
      +     * 
      +     * Where the second key is greater than the first.
      +     * 
      +     * 
      +     *    Stage 1
      +     *    =======
      +     *    
      +     *    :index : {
      +     *       :start { :next = n0 },
      +     *       n0 : {
      +     *          :next =,
      +     *          content : {
      +     *             foobar : {
      +     *                match = true
      +     *             }
      +     *          }
      +     *       }
      +     *    }
      +     *    
      +     *    Stage 2
      +     *    =======
      +     *    
      +     *    :index : {
      +     *       :start : { :next = n1 },
      +     *       n1 : {
      +     *          :next =,
      +     *          content : {
      +     *             foobar : {
      +     *                match = true
      +     *             }
      +     *          }
      +     *       }
      +     *    }
      +     * 
      +     */
      +
      +    @Test
      +    public void singleKeyUpdate() {
      +        final String N0 = KEYS[0];
      +        final String N1 = KEYS[1];
      +        final String PATH = "/content/foobar";
      +        final String[] NODES = Iterables.toArray(PathUtils.elements(PATH), String.class);
      +        IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy();
      +        NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder();
      +        NodeBuilder node = null;
      +
      +        // Stage 1
      +        store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0));
      +        node = index.getChildNode(START);
      +        assertTrue(":start should exists", node.exists());
      +        assertEquals(":start should point to n0", N0, node.getString(NEXT));
      +
      +        node = index.getChildNode(N0);
      +        assertTrue(":index should have n0", node.exists());
      +        assertTrue("n0 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT)));
      +
      +        node = node.getChildNode(NODES[0]);
      +        assertTrue("n0 should have /content", node.exists());
      +
      +        node = node.getChildNode(NODES[1]);
      +        assertTrue("/content should contain /foobar", node.exists());
      +        assertTrue("/foobar should have match=true", node.getBoolean("match"));
      +
      +        // Stage 2
      +        store.update(index, PATH, newHashSet(N0), newHashSet(N1));
      +        node = index.getChildNode(START);
      +        assertEquals(":start should now point to N1", N1, node.getString(NEXT));
      +
      +        node = index.getChildNode(N1);
      +        assertTrue("N1 should exists", node.exists());
      +        assertTrue("N1 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT)));
      +
      +        node = node.getChildNode(NODES[0]);
      +        assertTrue("N1 should have /content", node.exists());
      +
      +        node = node.getChildNode(NODES[1]);
      +        assertTrue("/content should contain /foobar", node.exists());
      +        assertTrue("/foobar should have match=true", node.getBoolean("match"));
      +    }
      +
      +    /**
      +     * 

      + * find a previous item given a key in an index with 1 element only + *

      + * + *

      + * it relies on the functionality of the store.update() for creating the + * index + *

      + * + * + * :index { + * :start : { :next=n0 }, + * n0 = { :next= } + * } + * + */ + @Test + public void findPrevious1ItemIndex() { + final OrderedContentMirrorStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + final String N0 = KEYS[0]; + final NodeState NODE_START = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, N0).getNodeState(); + final NodeState NODE_0 = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState(); + final NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + + index.setChildNode(START, NODE_START); + index.setChildNode(N0, NODE_0); + + NodeState indexState = index.getNodeState(); + ChildNodeEntry previous = store.findPrevious(indexState, NODE_0); + assertNotNull(previous); + assertEquals("the :start node is expected", NODE_START, previous.getNodeState()); + } + + /** + * test the use case where a document change the indexed property. For + * example document that change author. + * + *

      + * it relies on the functionality of the store.update() for creating the + * index + *

      + * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * content : { + * one { match=true }, + * two { match=true } + * } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = n1, + * content : { + * one : { match = true } + * } + * }, + * n1 : { + * :next = , + * content : { + * two : { match = true } + * } + * } + * } + * + */ + @Test + public void documentChangingKey() { + final String PATH0 = "/content/one"; + final String PATH1 = "/content/two"; + final String N0 = KEYS[0]; + final String N1 = KEYS[1]; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 - initialising the index + store.update(index, PATH0, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N0)); + + // ensuring the right structure + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertFalse(index.hasChildNode(N1)); + + NodeBuilder node = index.getChildNode(START); + assertEquals(":start pointing to wrong node", N0, node.getString(NEXT)); + + node = index.getChildNode(N0); + assertTrue("N0 should go nowhere", Strings.isNullOrEmpty(node.getString(NEXT))); + + // checking the first document + String[] path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + + path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = index.getChildNode(N0).getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + + // Stage 2 + store.update(index, PATH1, newHashSet(N0), newHashSet(N1)); + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertTrue(index.hasChildNode(N1)); + + node = index.getChildNode(START); + assertEquals(":start pointing to wrong node", N0, node.getString(NEXT)); + + node = index.getChildNode(N0); + assertEquals(N1, node.getString(NEXT)); + path = Iterables.toArray(PathUtils.elements(PATH0), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + path = Iterables.toArray(PathUtils.elements(PATH1), String.class); + node = index.getChildNode(N0).getChildNode(path[0]);// we know both the + // documents share + // the same /content + assertFalse("/content/two should no longer be under N0", node.hasChildNode(path[1])); + + node = index.getChildNode(N1); + assertTrue("N1 should point nowhere", Strings.isNullOrEmpty(node.getString(NEXT))); + path = Iterables.toArray(PathUtils.elements(PATH1), String.class); + node = node.getChildNode(path[0]); + assertTrue(node.exists()); + node = node.getChildNode(path[1]); + assertTrue(node.exists()); + assertTrue(node.getBoolean("match")); + } + + /** + * test when a document is deleted and is the only one under the indexed key + * + *

      + * it relies on the functionality of the store.update() for creating the + * index + *

      + * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * sampledoc : { match = true } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = } + * } + * + */ + @Test + public void deleteTheOnlyDocument() { + final String N0 = KEYS[0]; + final String PATH = "/sampledoc"; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 - initialising the index + store.update(index, PATH, EMPTY_KEY_SET, newHashSet(N0)); + + // we assume it works and therefore not checking the status of the index + // let's go straight to Stage 2 + + // Stage 2 + store.update(index, PATH, newHashSet(N0), EMPTY_KEY_SET); + assertFalse("The node should have been removed", index.hasChildNode(N0)); + assertTrue("as the index should be empty, :start should point nowhere", + Strings.isNullOrEmpty(index.getChildNode(START).getString(NEXT))); + } + + /** + * test when the document is deleted but there're still some documents left + * under the indexed key + * + *

      + * it relies on the functionality of the store.update() for creating the + * index + *

      + * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next = , + * doc1 : { match=true }, + * doc2 : { match=true } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n0 }, + * n0 : { + * :next =, + * doc2 : { match = true } + * } + * } + * + */ + @Test + public void deleteOneOfTheDocuments() { + final String N0 = KEYS[0]; + final String DOC1 = "doc1"; + final String DOC2 = "doc2"; + final String PATH1 = "/" + DOC1; + final String PATH2 = "/" + DOC2; + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH2, EMPTY_KEY_SET, newHashSet(N0)); + + // we trust the store at this point and skip a double-check. Let's move + // to Stage 2! + + store.update(index, PATH1, newHashSet(N0), EMPTY_KEY_SET); + + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertEquals(":start should still point to N0", N0, index.getChildNode(START).getString(NEXT)); + assertTrue("n0 should point nowhere", Strings.isNullOrEmpty(index.getChildNode(N0).getString(NEXT))); + + assertFalse(index.getChildNode(N0).hasChildNode(DOC1)); + assertTrue(index.getChildNode(N0).hasChildNode(DOC2)); + assertTrue(index.getChildNode(N0).getChildNode(DOC2).getBoolean("match")); + } + + /** + * test when the only document is deleted from an indexed key but there're + * still some keys left in the index + * + *

      + * it relies on the functionality of the store.update() for creating the + * index + *

      + * + * + * Stage 1 + * ======= + * + * :index : { + * :start : { :next = n1 }, + * n0 : { + * :next = , + * content : { + * doc0 : { match = true } + * } + * }, + * n1 : { + * :next = n2, + * content : { + * doc1 : { match = true } + * } + * } + * n2 : { + * :next = n0, + * content : { + * doc2 : { match = true } + * } + * } + * } + * + * Stage 2 + * ======= + * + * :index : { + * :start : { :next = n1 }, + * n0 : { + * :next = , + * content : { + * doc0 : { match = true } + * } + * }, + * n1 : { + * :next = n0, + * content : { + * doc1 : { match = true } + * } + * } + * } + * + * + */ + @Test + public void deleteTheOnlyDocumentInMultiKeysIndex() { + final String PATH0 = "/content/doc0"; + final String PATH1 = "/content/doc1"; + final String PATH2 = "/content/doc2"; + final String N0 = KEYS[2]; + final String N1 = KEYS[0]; + final String N2 = KEYS[1]; + + NodeBuilder index = EmptyNodeState.EMPTY_NODE.builder(); + IndexStoreStrategy store = new OrderedContentMirrorStoreStrategy(); + + // Stage 1 + store.update(index, PATH0, EMPTY_KEY_SET, newHashSet(N0)); + store.update(index, PATH1, EMPTY_KEY_SET, newHashSet(N1)); + store.update(index, PATH2, EMPTY_KEY_SET, newHashSet(N2)); + + // as we trust the store we skip the check and goes straight to Stage 2. + + // Stage 2 + store.update(index, PATH2, newHashSet(N2), EMPTY_KEY_SET); + + // checking key nodes + assertTrue(index.hasChildNode(START)); + assertTrue(index.hasChildNode(N0)); + assertTrue(index.hasChildNode(N1)); + assertFalse(index.hasChildNode(N2)); + + // checking pointers + assertEquals(N1, index.getChildNode(START).getString(NEXT)); + assertEquals(N0, index.getChildNode(N1).getString(NEXT)); + assertTrue(Strings.isNullOrEmpty(index.getChildNode(N0).getString(NEXT))); + + // checking sub-nodes + String[] subNodes = Iterables.toArray(PathUtils.elements(PATH0), String.class); + assertTrue(index.getChildNode(N0).hasChildNode(subNodes[0])); + assertTrue(index.getChildNode(N0).getChildNode(subNodes[0]).hasChildNode(subNodes[1])); + assertTrue(index.getChildNode(N0).getChildNode(subNodes[0]).getChildNode(subNodes[1]).getBoolean("match")); + + subNodes = Iterables.toArray(PathUtils.elements(PATH1), String.class); + assertTrue(index.getChildNode(N1).hasChildNode(subNodes[0])); + assertTrue(index.getChildNode(N1).getChildNode(subNodes[0]).hasChildNode(subNodes[1])); + assertTrue(index.getChildNode(N1).getChildNode(subNodes[0]).getChildNode(subNodes[1]).getBoolean("match")); + } +} diff --git a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java index 7352cf4..933d0da 100644 --- a/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java +++ b/oak-jcr/src/main/java/org/apache/jackrabbit/oak/jcr/Jcr.java @@ -30,6 +30,7 @@ import org.apache.jackrabbit.oak.plugins.commit.ConflictValidatorProvider; import org.apache.jackrabbit.oak.plugins.commit.JcrConflictHandler; import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.nodetype.NodeTypeIndexProvider; +import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexProvider; import org.apache.jackrabbit.oak.plugins.index.reference.ReferenceEditorProvider; @@ -79,6 +80,8 @@ public class Jcr { with(new PropertyIndexProvider()); with(new NodeTypeIndexProvider()); + + with(new OrderedPropertyIndexEditorProvider()); } public Jcr() { diff --git a/oak-run/pom.xml b/oak-run/pom.xml index 00c3fcb..ea23235 100644 --- a/oak-run/pom.xml +++ b/oak-run/pom.xml @@ -192,6 +192,9 @@ junit test + + com.google.code.findbugs + jsr305 + - diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java index 0139b63..54b5ecc 100644 --- a/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/BenchmarkRunner.java @@ -97,6 +97,13 @@ public class BenchmarkRunner { base.value(options), 256, cacheSize, mmap.value(options)) }; Benchmark[] allBenchmarks = new Benchmark[] { + new OrderedIndexQueryOrderedIndexTest(), + new OrderedIndexQueryStandardIndexTest(), + new OrderedIndexQueryNoIndexTest(), + new OrderedIndexInsertOrderedPropertyTest(), + new OrderedIndexInsertStandardPropertyTest(), + new OrderedIndexInsertNoIndexTest(), + new OrderByQueryTest(), new LoginTest(), new LoginLogoutTest(), new NamespaceTest(), diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java new file mode 100644 index 0000000..a6cabdb --- /dev/null +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderByQueryTest.java @@ -0,0 +1,89 @@ +/* + * 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.benchmark; + +import java.util.Random; +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.query.QueryResult; + +/** + * This benchmark measures the read performance of child nodes using + * an ORDER BY query. + *

      + * This is related to OAK-1263. + * + */ +public class OrderByQueryTest extends AbstractTest { + + private static final String NT = "oak:unstructured"; + + private static final String ROOT_NODE_NAME = "test" + TEST_ID; + private static final int NUM_NODES = 10000; + private static final String PROPERTY_NAME = "testProperty"; + private static final Random random = new Random(); // doesn't have to be very secure, just some randomness + + @Override + protected void beforeSuite() throws Exception { + Session session = loginWriter(); + Node rootNode = session.getRootNode(); + if (rootNode.hasNode(ROOT_NODE_NAME)) { + Node root = rootNode.getNode(ROOT_NODE_NAME); + root.remove(); + } + rootNode = session.getRootNode().addNode(ROOT_NODE_NAME, NT); + + for (int i = 0; i < NUM_NODES; i++) { + if (i%1000==0) { + session.save(); + } + Node newNode = rootNode.addNode(UUID.randomUUID().toString(), NT); + newNode.setProperty(PROPERTY_NAME, random.nextLong()); + } + session.save(); + } + + @Override + public void runTest() throws Exception { + final Session session = loginWriter(); + try { + // run the query + final QueryManager qm = session.getWorkspace().getQueryManager(); + + final Query q = + qm.createQuery("SELECT * FROM [oak:unstructured] AS s WHERE " + + "ISDESCENDANTNODE(s, [/"+ROOT_NODE_NAME+"/]) ORDER BY s."+PROPERTY_NAME+"]", + Query.JCR_SQL2); + final QueryResult res = q.execute(); + + final NodeIterator nit = res.getNodes(); +// while(nit.hasNext()) { +// Node node = nit.nextNode(); +//// System.out.println("node: "+node.getPath()+", prop="+node.getProperty(PROPERTY_NAME).getLong()); +// } + } catch (RepositoryException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } +} diff --git a/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java new file mode 100644 index 0000000..674ab89 --- /dev/null +++ b/oak-run/src/main/java/org/apache/jackrabbit/oak/benchmark/OrderedIndexBaseTest.java @@ -0,0 +1,112 @@ +/* + * 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.benchmark; + +import java.util.UUID; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.oak.benchmark.util.OakIndexUtils; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; +import org.apache.jackrabbit.oak.plugins.index.property.OrderedPropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider; +import org.apache.jackrabbit.oak.plugins.nodetype.NodeTypeConstants; + +/** + * + */ +public abstract class OrderedIndexBaseTest extends AbstractTest { + /** + * the number of nodes created per iteration + */ + static final int NODES_PER_ITERATION = Integer.parseInt(System.getProperty("nodesPerIteration", "100")); + + /** + * number of nodes that has to be added before performing the actual test + */ + static final int PRE_ADDED_NODES = Integer.parseInt(System.getProperty("preAddedNodes", "0")); + + /** + * type of the created node + */ + static final String NODE_TYPE = NodeTypeConstants.NT_OAK_UNSTRUCTURED; + + /** + * property that will be indexed + */ + static final String INDEXED_PROPERTY = "indexedProperty"; + + /** + * node name below which creating the test data + */ + final String DUMP_NODE = this.getClass().getSimpleName() + TEST_ID; + + /** + * session used for operations throughout the test + */ + Session session; + + /** + * node under which all the test data will be filled in + */ + Node dump; + + void insertRandomNodes(int numberOfNodes){ + try{ + for(int i=0; i Date: Mon, 3 Mar 2014 12:15:11 +0100 Subject: [PATCH 2/7] OAK-1263 fixing Apache headers --- .../index/property/OrderedPropertyIndexEditor.java | 17 +++++++++++++++++ .../property/OrderedPropertyIndexEditorProvider.java | 17 +++++++++++++++++ .../index/property/OrderedPropertyIndexEditorTest.java | 17 +++++++++++++++++ 3 files changed, 51 insertions(+) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java index 46807f7..8a02f2e 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java @@ -1,3 +1,20 @@ +/* + * 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.Collections; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java index 75c439d..3ea60ae 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java @@ -1,3 +1,20 @@ +/* + * 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 javax.annotation.CheckForNull; diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java index 34f4562..f75fbdb 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorTest.java @@ -1,3 +1,20 @@ +/* + * 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.easymock.EasyMock.createNiceMock; -- 1.8.3.4 (Apple Git-47) From 1fec81e8c7eea22eed52342028344894fb7c466f Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Mon, 3 Mar 2014 12:15:55 +0100 Subject: [PATCH 3/7] OAK-1263 fixing formatting --- .../jackrabbit/oak/plugins/index/IndexUtils.java | 208 +++--- .../strategy/ContentMirrorStoreStrategy.java | 725 ++++++++++----------- 2 files changed, 468 insertions(+), 465 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java index ec920c1..4633e9b 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java @@ -49,115 +49,119 @@ import org.apache.jackrabbit.oak.util.NodeUtil; */ public class IndexUtils { - public static NodeBuilder getOrCreateOakIndex(NodeBuilder root) { - NodeBuilder index; - if (!root.hasChildNode(INDEX_DEFINITIONS_NAME)) { - index = root.child(INDEX_DEFINITIONS_NAME); - // TODO: use property node type name - index.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); - } else { - index = root.child(INDEX_DEFINITIONS_NAME); - } - return index; - } + public static NodeBuilder getOrCreateOakIndex(NodeBuilder root) { + NodeBuilder index; + if (!root.hasChildNode(INDEX_DEFINITIONS_NAME)) { + index = root.child(INDEX_DEFINITIONS_NAME); + // TODO: use property node type name + index.setProperty(JCR_PRIMARYTYPE, NT_UNSTRUCTURED, NAME); + } else { + index = root.child(INDEX_DEFINITIONS_NAME); + } + return index; + } - /** - * Create a new property index definition below the given {@code indexNode}. - * - * @param index The oak:index node builder - * @param indexDefName The name of the new property index. - * @param reindex {@code true} if the the reindex flag should be turned on. - * @param unique {@code true} if the index is expected the assert property - * uniqueness. - * @param propertyNames The property names that should be indexed. - * @param declaringNodeTypeNames The declaring node type names or {@code null}. - * @return the NodeBuilder of the new index definition. - */ - public static NodeBuilder createIndexDefinition(@Nonnull NodeBuilder index, - @Nonnull String indexDefName, - boolean reindex, - boolean unique, - @Nonnull Collection propertyNames, - @Nullable Collection declaringNodeTypeNames) { - NodeBuilder entry = index.child(indexDefName) - .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + /** + * Create a new property index definition below the given {@code indexNode}. + * + * @param index + * The oak:index node builder + * @param indexDefName + * The name of the new property index. + * @param reindex + * {@code true} if the the reindex flag should be turned on. + * @param unique + * {@code true} if the index is expected the assert property uniqueness. + * @param propertyNames + * The property names that should be indexed. + * @param declaringNodeTypeNames + * The declaring node type names or {@code null}. + * @return the NodeBuilder of the new index definition. + */ + public static NodeBuilder createIndexDefinition(@Nonnull + NodeBuilder index, @Nonnull + String indexDefName, boolean reindex, boolean unique, @Nonnull + Collection propertyNames, @Nullable + Collection declaringNodeTypeNames) { + NodeBuilder entry = index.child(indexDefName).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) .setProperty(REINDEX_PROPERTY_NAME, reindex); - if (unique) { - entry.setProperty(UNIQUE_PROPERTY_NAME, unique); - } - entry.setProperty(PropertyStates.createProperty(PROPERTY_NAMES, propertyNames, NAMES)); - if (declaringNodeTypeNames != null && !declaringNodeTypeNames.isEmpty()) { - entry.setProperty(PropertyStates.createProperty(DECLARING_NODE_TYPES, declaringNodeTypeNames, NAMES)); - } - return entry; - } + if (unique) { + entry.setProperty(UNIQUE_PROPERTY_NAME, unique); + } + entry.setProperty(PropertyStates.createProperty(PROPERTY_NAMES, propertyNames, NAMES)); + if (declaringNodeTypeNames != null && !declaringNodeTypeNames.isEmpty()) { + entry.setProperty(PropertyStates.createProperty(DECLARING_NODE_TYPES, declaringNodeTypeNames, NAMES)); + } + return entry; + } - /** - * Create a new property2 index definition below the given {@code indexNode}. - * - * @param indexNode - * @param indexDefName - * @param unique - * @param propertyNames - * @param declaringNodeTypeNames - */ - public static void createIndexDefinition(@Nonnull NodeUtil indexNode, - @Nonnull String indexDefName, - boolean unique, - @Nonnull String[] propertyNames, - @Nullable String[] declaringNodeTypeNames) throws RepositoryException { - - createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, PropertyIndexEditorProvider.TYPE); - } + /** + * Create a new property2 index definition below the given {@code indexNode}. + * + * @param indexNode + * @param indexDefName + * @param unique + * @param propertyNames + * @param declaringNodeTypeNames + */ + public static void createIndexDefinition(@Nonnull + NodeUtil indexNode, @Nonnull + String indexDefName, boolean unique, @Nonnull + String[] propertyNames, @Nullable + String[] declaringNodeTypeNames) throws RepositoryException { - /** - * Create a new property index definition below the given {@code indexNode} of the provided {@code propertyIndexType}. - * @param indexNode - * @param indexDefName - * @param unique - * @param propertyNames - * @param declaringNodeTypeNames - * @param propertyIndexType - * @throws RepositoryException - */ - public static void createIndexDefinition(@Nonnull NodeUtil indexNode, - @Nonnull String indexDefName, - boolean unique, - @Nonnull String[] propertyNames, - @Nullable String[] declaringNodeTypeNames, - @Nonnull String propertyIndexType) throws RepositoryException { - NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); - entry.setString(TYPE_PROPERTY_NAME, propertyIndexType); - entry.setBoolean(REINDEX_PROPERTY_NAME, true); - if (unique) { - entry.setBoolean(UNIQUE_PROPERTY_NAME, true); - } - if (declaringNodeTypeNames != null && declaringNodeTypeNames.length > 0) { - entry.setNames(DECLARING_NODE_TYPES, declaringNodeTypeNames); - } - entry.setNames(PROPERTY_NAMES, propertyNames); - } + createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, + PropertyIndexEditorProvider.TYPE); + } - public static void createReferenceIndex(@Nonnull NodeBuilder index) { - index.child(NodeReferenceConstants.NAME) - .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); - } + /** + * Create a new property index definition below the given {@code indexNode} of the provided + * {@code propertyIndexType}. + * + * @param indexNode + * @param indexDefName + * @param unique + * @param propertyNames + * @param declaringNodeTypeNames + * @param propertyIndexType + * @throws RepositoryException + */ + public static void createIndexDefinition(@Nonnull + NodeUtil indexNode, @Nonnull + String indexDefName, boolean unique, @Nonnull + String[] propertyNames, @Nullable + String[] declaringNodeTypeNames, @Nonnull + String propertyIndexType) throws RepositoryException { + NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); + entry.setString(TYPE_PROPERTY_NAME, propertyIndexType); + entry.setBoolean(REINDEX_PROPERTY_NAME, true); + if (unique) { + entry.setBoolean(UNIQUE_PROPERTY_NAME, true); + } + if (declaringNodeTypeNames != null && declaringNodeTypeNames.length > 0) { + entry.setNames(DECLARING_NODE_TYPES, declaringNodeTypeNames); + } + entry.setNames(PROPERTY_NAMES, propertyNames); + } - public static boolean isIndexNodeType(NodeState state) { - PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); - return ps != null - && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); - } + public static void createReferenceIndex(@Nonnull + NodeBuilder index) { + index.child(NodeReferenceConstants.NAME).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); + } - public static boolean isIndexNodeType(NodeState state, String typeIn) { - if (!isIndexNodeType(state)) { - return false; - } - PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); - return type != null && !type.isArray() - && type.getValue(Type.STRING).equals(typeIn); - } + public static boolean isIndexNodeType(NodeState state) { + PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); + return ps != null && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); + } + + public static boolean isIndexNodeType(NodeState state, String typeIn) { + if (!isIndexNodeType(state)) { + return false; + } + PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); + return type != null && !type.isArray() && type.getValue(Type.STRING).equals(typeIn); + } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java index 95b640c..9cc2a84 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java @@ -44,14 +44,12 @@ import com.google.common.collect.Queues; import com.google.common.collect.Sets; /** - * An IndexStoreStrategy implementation that saves the nodes under a hierarchy - * that mirrors the repository tree.
      - * This should minimize the chance that concurrent updates overlap on the same - * content node.
      + * An IndexStoreStrategy implementation that saves the nodes under a hierarchy that mirrors the repository tree.
      + * This should minimize the chance that concurrent updates overlap on the same content node.
      *
      - * For example for a node that is under {@code /test/node}, the index - * structure will be {@code /oak:index/index/test/node}: - * + * For example for a node that is under {@code /test/node}, the index structure will be + * {@code /oak:index/index/test/node}: + * *

        * {@code
        * /
      @@ -63,374 +61,375 @@ import com.google.common.collect.Sets;
        *         node
        * }
        * 
      - * + * */ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { - static final Logger LOG = LoggerFactory.getLogger(ContentMirrorStoreStrategy.class); - - @Override - public void update( - NodeBuilder index, String path, - Set beforeKeys, Set afterKeys) { - for (String key : beforeKeys) { - remove(index, key, path); - } - for (String key : afterKeys) { - insert(index, key, path); - } - } - - private void remove(NodeBuilder index, String key, String value) { - NodeBuilder builder = index.getChildNode(key); - if (builder.exists()) { - // Collect all builders along the given path - Deque builders = newArrayDeque(); - builders.addFirst(builder); - - // Descend to the correct location in the index tree - for (String name : PathUtils.elements(value)) { - builder = builder.getChildNode(name); + static final Logger LOG = LoggerFactory.getLogger(ContentMirrorStoreStrategy.class); + + @Override + public void update(NodeBuilder index, String path, Set beforeKeys, Set afterKeys) { + for (String key : beforeKeys) { + remove(index, key, path); + } + for (String key : afterKeys) { + insert(index, key, path); + } + } + + private void remove(NodeBuilder index, String key, String value) { + NodeBuilder builder = index.getChildNode(key); + if (builder.exists()) { + // Collect all builders along the given path + Deque builders = newArrayDeque(); builders.addFirst(builder); - } - - // Drop the match value, if present - if (builder.exists()) { - builder.removeProperty("match"); - } - - // Prune all index nodes that are no longer needed - prune(index, builders); - } - } - - /** - * Physically prune a list of nodes from the index - * - * @param index the current index - * @param builders list of nodes to prune - */ - void prune(final NodeBuilder index, final Deque builders){ - for (NodeBuilder node : builders) { - if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) { - return; - } else if (node.exists()) { - node.remove(); - } - } - } - - private void insert(NodeBuilder index, String key, String value) { - // NodeBuilder builder = index.child(key); - NodeBuilder builder = fetchKeyNode(index, key); - for (String name : PathUtils.elements(value)) { - builder = builder.child(name); - } - builder.setProperty("match", true); - } - - /** - * fetch from the index the key node - * - * @param index the current index root - * @param key the 'key' to fetch from the repo - * @return the node representing the key - */ - NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index,@Nonnull String key){ - return index.child(key); - } - - public Iterable query(final Filter filter, final String indexName, - final NodeState indexMeta, final String indexStorageNodeName, - final Iterable values) { - final NodeState index = indexMeta.getChildNode(indexStorageNodeName); - return new Iterable() { - @Override - public Iterator iterator() { - PathIterator it = new PathIterator(filter, indexName); - if (values == null) { - it.setPathContainsValue(true); - it.enqueue(getChildNodeEntries(index).iterator()); - } else { - for (String p : values) { - NodeState property = index.getChildNode(p); - if (property.exists()) { - // we have an entry for this value, so use it - it.enqueue(Iterators.singletonIterator( - new MemoryChildNodeEntry("", property))); - } - } + + // Descend to the correct location in the index tree + for (String name : PathUtils.elements(value)) { + builder = builder.getChildNode(name); + builders.addFirst(builder); } - return it; - } - }; - } - - @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index){ - return index.getChildNodeEntries(); - } - - @Override - public Iterable query(final Filter filter, final String indexName, - final NodeState indexMeta, final Iterable values) { - return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values); - } - - @Override - public long count(NodeState indexMeta, Set values, int max) { - return count(indexMeta, INDEX_CONTENT_NODE_NAME, values, max); - } - - public long count(NodeState indexMeta, final String indexStorageNodeName, - Set values, int max) { - NodeState index = indexMeta.getChildNode(indexStorageNodeName); - int count = 0; - if (values == null) { - PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME); - if (ec != null) { - return ec.getValue(Type.LONG); - } - CountingNodeVisitor v = new CountingNodeVisitor(max); - v.visit(index); - count = v.getEstimatedCount(); - // "is not null" queries typically read more data - count *= 10; - } else { - int size = values.size(); - if (size == 0) { - return 0; - } - max = Math.max(10, max / size); - int i = 0; - for (String p : values) { - if (count > max && i > 3) { - // the total count is extrapolated from the the number - // of values counted so far to the total number of values - count = count * size / i; - break; + + // Drop the match value, if present + if (builder.exists()) { + builder.removeProperty("match"); } - NodeState s = index.getChildNode(p); - if (s.exists()) { - CountingNodeVisitor v = new CountingNodeVisitor(max); - v.visit(s); - count += v.getEstimatedCount(); + + // Prune all index nodes that are no longer needed + prune(index, builders); + } + } + + /** + * Physically prune a list of nodes from the index + * + * @param index + * the current index + * @param builders + * list of nodes to prune + */ + void prune(final NodeBuilder index, final Deque builders) { + for (NodeBuilder node : builders) { + if (node.getBoolean("match") || node.getChildNodeCount(1) > 0) { + return; + } else if (node.exists()) { + node.remove(); } - i++; - } - } - return count; - } - - /** - * An iterator over paths within an index node. - */ - static class PathIterator implements Iterator { - - private final Filter filter; - private final String indexName; - private final Deque> nodeIterators = - Queues.newArrayDeque(); - private int readCount; - private boolean init; - private boolean closed; - private String parentPath; - private String currentPath; - private boolean pathContainsValue; - - /** - * Keep the returned path, to avoid returning duplicate entries. - */ - private final Set knownPaths = Sets.newHashSet(); - - PathIterator(Filter filter, String indexName) { - this.filter = filter; - this.indexName = indexName; - parentPath = ""; - currentPath = "/"; - } - - void enqueue(Iterator it) { - nodeIterators.addLast(it); - } - - void setPathContainsValue(boolean pathContainsValue) { - if (init) { - throw new IllegalStateException("This iterator is already initialized"); - } - this.pathContainsValue = pathContainsValue; - } - - @Override - public boolean hasNext() { - if (!closed && !init) { - fetchNext(); - init = true; - } - return !closed; - } - - private void fetchNext() { - while (true) { - fetchNextPossiblyDuplicate(); - if (closed) { - return; + } + } + + private void insert(NodeBuilder index, String key, String value) { + // NodeBuilder builder = index.child(key); + NodeBuilder builder = fetchKeyNode(index, key); + for (String name : PathUtils.elements(value)) { + builder = builder.child(name); + } + builder.setProperty("match", true); + } + + /** + * fetch from the index the key node + * + * @param index + * the current index root + * @param key + * the 'key' to fetch from the repo + * @return the node representing the key + */ + NodeBuilder fetchKeyNode(@Nonnull + NodeBuilder index, @Nonnull + String key) { + return index.child(key); + } + + public Iterable query(final Filter filter, final String indexName, final NodeState indexMeta, + final String indexStorageNodeName, final Iterable values) { + final NodeState index = indexMeta.getChildNode(indexStorageNodeName); + return new Iterable() { + @Override + public Iterator iterator() { + PathIterator it = new PathIterator(filter, indexName); + if (values == null) { + it.setPathContainsValue(true); + it.enqueue(getChildNodeEntries(index).iterator()); + } else { + for (String p : values) { + NodeState property = index.getChildNode(p); + if (property.exists()) { + // we have an entry for this value, so use it + it.enqueue(Iterators.singletonIterator(new MemoryChildNodeEntry("", property))); + } + } + } + return it; + } + }; + } + + @Nonnull + Iterable getChildNodeEntries(@Nonnull + final NodeState index) { + return index.getChildNodeEntries(); + } + + @Override + public Iterable query(final Filter filter, final String indexName, final NodeState indexMeta, + final Iterable values) { + return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values); + } + + @Override + public long count(NodeState indexMeta, Set values, int max) { + return count(indexMeta, INDEX_CONTENT_NODE_NAME, values, max); + } + + public long count(NodeState indexMeta, final String indexStorageNodeName, Set values, int max) { + NodeState index = indexMeta.getChildNode(indexStorageNodeName); + int count = 0; + if (values == null) { + PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME); + if (ec != null) { + return ec.getValue(Type.LONG); + } + CountingNodeVisitor v = new CountingNodeVisitor(max); + v.visit(index); + count = v.getEstimatedCount(); + // "is not null" queries typically read more data + count *= 10; + } else { + int size = values.size(); + if (size == 0) { + return 0; } - if (pathContainsValue) { - String value = PathUtils.elements(currentPath).iterator().next(); - currentPath = PathUtils.relativize(value, currentPath); - // don't return duplicate paths: - // Set.add returns true if the entry was new, - // so if it returns false, it was already known - if (!knownPaths.add(currentPath)) { - continue; - } + max = Math.max(10, max / size); + int i = 0; + for (String p : values) { + if (count > max && i > 3) { + // the total count is extrapolated from the the number + // of values counted so far to the total number of values + count = count * size / i; + break; + } + NodeState s = index.getChildNode(p); + if (s.exists()) { + CountingNodeVisitor v = new CountingNodeVisitor(max); + v.visit(s); + count += v.getEstimatedCount(); + } + i++; } - break; - } - } - - private void fetchNextPossiblyDuplicate() { - while (!nodeIterators.isEmpty()) { - Iterator iterator = nodeIterators.getLast(); - if (iterator.hasNext()) { - ChildNodeEntry entry = iterator.next(); - - readCount++; - if (readCount % 1000 == 0) { - FilterIterators.checkReadLimit(readCount); - LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " + filter); - } - - NodeState node = entry.getNodeState(); - - String name = entry.getName(); - if (NodeStateUtils.isHidden(name)) { - continue; - } - currentPath = PathUtils.concat(parentPath, name); - - nodeIterators.addLast(node.getChildNodeEntries().iterator()); - parentPath = currentPath; - - if (node.getBoolean("match")) { - return; - } - - } else { - nodeIterators.removeLast(); - parentPath = PathUtils.getParentPath(parentPath); + } + return count; + } + + /** + * An iterator over paths within an index node. + */ + static class PathIterator implements Iterator { + + private final Filter filter; + private final String indexName; + private final Deque> nodeIterators = Queues.newArrayDeque(); + private int readCount; + private boolean init; + private boolean closed; + private String parentPath; + private String currentPath; + private boolean pathContainsValue; + + /** + * Keep the returned path, to avoid returning duplicate entries. + */ + private final Set knownPaths = Sets.newHashSet(); + + PathIterator(Filter filter, String indexName) { + this.filter = filter; + this.indexName = indexName; + parentPath = ""; + currentPath = "/"; + } + + void enqueue(Iterator it) { + nodeIterators.addLast(it); + } + + void setPathContainsValue(boolean pathContainsValue) { + if (init) { + throw new IllegalStateException("This iterator is already initialized"); } - } - currentPath = null; - closed = true; - } - - @Override - public String next() { - if (closed) { - throw new IllegalStateException("This iterator is closed"); - } - if (!init) { + this.pathContainsValue = pathContainsValue; + } + + @Override + public boolean hasNext() { + if (!closed && !init) { + fetchNext(); + init = true; + } + return !closed; + } + + private void fetchNext() { + while (true) { + fetchNextPossiblyDuplicate(); + if (closed) { + return; + } + if (pathContainsValue) { + String value = PathUtils.elements(currentPath).iterator().next(); + currentPath = PathUtils.relativize(value, currentPath); + // don't return duplicate paths: + // Set.add returns true if the entry was new, + // so if it returns false, it was already known + if (!knownPaths.add(currentPath)) { + continue; + } + } + break; + } + } + + private void fetchNextPossiblyDuplicate() { + while (!nodeIterators.isEmpty()) { + Iterator iterator = nodeIterators.getLast(); + if (iterator.hasNext()) { + ChildNodeEntry entry = iterator.next(); + + readCount++; + if (readCount % 1000 == 0) { + FilterIterators.checkReadLimit(readCount); + LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " + + filter); + } + + NodeState node = entry.getNodeState(); + + String name = entry.getName(); + if (NodeStateUtils.isHidden(name)) { + continue; + } + currentPath = PathUtils.concat(parentPath, name); + + nodeIterators.addLast(node.getChildNodeEntries().iterator()); + parentPath = currentPath; + + if (node.getBoolean("match")) { + return; + } + + } else { + nodeIterators.removeLast(); + parentPath = PathUtils.getParentPath(parentPath); + } + } + currentPath = null; + closed = true; + } + + @Override + public String next() { + if (closed) { + throw new IllegalStateException("This iterator is closed"); + } + if (!init) { + fetchNext(); + init = true; + } + String result = currentPath; fetchNext(); - init = true; - } - String result = currentPath; - fetchNext(); - return result; - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - - } - - /** - * A node visitor to recursively traverse a number of nodes. - */ - interface NodeVisitor { - void visit(NodeState state); - } - - /** - * A node visitor that counts the number of matching nodes up to a given - * maximum, in order to estimate the number of matches. - */ - static class CountingNodeVisitor implements NodeVisitor { - - /** - * The maximum number of matching nodes to count. - */ - final int maxCount; - - /** - * The current count of matching nodes. - */ - int count; - - /** - * The current depth (number of parent nodes). - */ - int depth; - - /** - * The sum of the depth of all matching nodes. This value is used to - * calculate the average depth. - */ - long depthTotal; - - CountingNodeVisitor(int maxCount) { - this.maxCount = maxCount; - } - - @Override - public void visit(NodeState state) { - if (state.hasProperty("match")) { - count++; - depthTotal += depth; - } - if (count < maxCount) { - depth++; - for (ChildNodeEntry entry : state.getChildNodeEntries()) { - if (count >= maxCount) { - break; - } - visit(entry.getNodeState()); + return result; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + + } + + /** + * A node visitor to recursively traverse a number of nodes. + */ + interface NodeVisitor { + void visit(NodeState state); + } + + /** + * A node visitor that counts the number of matching nodes up to a given maximum, in order to estimate the number of + * matches. + */ + static class CountingNodeVisitor implements NodeVisitor { + + /** + * The maximum number of matching nodes to count. + */ + final int maxCount; + + /** + * The current count of matching nodes. + */ + int count; + + /** + * The current depth (number of parent nodes). + */ + int depth; + + /** + * The sum of the depth of all matching nodes. This value is used to calculate the average depth. + */ + long depthTotal; + + CountingNodeVisitor(int maxCount) { + this.maxCount = maxCount; + } + + @Override + public void visit(NodeState state) { + if (state.hasProperty("match")) { + count++; + depthTotal += depth; } - depth--; - } - } - - /** - * The number of matches (at most the maximum count). - * - * @return the match count - */ - int getCount() { - return count; - } - - /** - * The number of estimated matches. This value might be higher than the - * number of counted matches, if the maximum number of matches has been - * reached. It is based on the average depth of matches, and the average - * number of child nodes. - * - * @return the estimated matches - */ - int getEstimatedCount() { - if (count < maxCount) { + if (count < maxCount) { + depth++; + for (ChildNodeEntry entry : state.getChildNodeEntries()) { + if (count >= maxCount) { + break; + } + visit(entry.getNodeState()); + } + depth--; + } + } + + /** + * The number of matches (at most the maximum count). + * + * @return the match count + */ + int getCount() { return count; - } - double averageDepth = (int) (depthTotal / count); - // the number of estimated matches is higher - // the higher the average depth of the first hits - long estimatedNodes = (long) (count * Math.pow(1.1, averageDepth)); - estimatedNodes = Math.min(estimatedNodes, Integer.MAX_VALUE); - return Math.max(count, (int) estimatedNodes); - } - - } + } + + /** + * The number of estimated matches. This value might be higher than the number of counted matches, if the + * maximum number of matches has been reached. It is based on the average depth of matches, and the average + * number of child nodes. + * + * @return the estimated matches + */ + int getEstimatedCount() { + if (count < maxCount) { + return count; + } + double averageDepth = (int) (depthTotal / count); + // the number of estimated matches is higher + // the higher the average depth of the first hits + long estimatedNodes = (long) (count * Math.pow(1.1, averageDepth)); + estimatedNodes = Math.min(estimatedNodes, Integer.MAX_VALUE); + return Math.max(count, (int) estimatedNodes); + } + + } } -- 1.8.3.4 (Apple Git-47) From 8cd39408e41885a3d7770deff1ebf65c822657fb Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Mon, 3 Mar 2014 12:33:52 +0100 Subject: [PATCH 4/7] OAK-1263 fixing formatting --- .../jackrabbit/oak/plugins/index/IndexUtils.java | 64 +++++++++------------- 1 file changed, 27 insertions(+), 37 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java index 4633e9b..bcc394f 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java @@ -64,28 +64,21 @@ public class IndexUtils { /** * Create a new property index definition below the given {@code indexNode}. * - * @param index - * The oak:index node builder - * @param indexDefName - * The name of the new property index. - * @param reindex - * {@code true} if the the reindex flag should be turned on. - * @param unique - * {@code true} if the index is expected the assert property uniqueness. - * @param propertyNames - * The property names that should be indexed. - * @param declaringNodeTypeNames - * The declaring node type names or {@code null}. + * @param index The oak:index node builder + * @param indexDefName The name of the new property index. + * @param reindex {@code true} if the the reindex flag should be turned on. + * @param unique {@code true} if the index is expected the assert property uniqueness. + * @param propertyNames The property names that should be indexed. + * @param declaringNodeTypeNames The declaring node type names or {@code null}. * @return the NodeBuilder of the new index definition. */ - public static NodeBuilder createIndexDefinition(@Nonnull - NodeBuilder index, @Nonnull - String indexDefName, boolean reindex, boolean unique, @Nonnull - Collection propertyNames, @Nullable - Collection declaringNodeTypeNames) { - NodeBuilder entry = index.child(indexDefName).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) - .setProperty(REINDEX_PROPERTY_NAME, reindex); + public static NodeBuilder createIndexDefinition(@Nonnull NodeBuilder index, + @Nonnull String indexDefName, + boolean reindex, + boolean unique, + @Nonnull Collection propertyNames, + @Nullable Collection declaringNodeTypeNames) { + NodeBuilder entry = index.child(indexDefName).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME).setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE).setProperty(REINDEX_PROPERTY_NAME, reindex); if (unique) { entry.setProperty(UNIQUE_PROPERTY_NAME, unique); } @@ -105,19 +98,17 @@ public class IndexUtils { * @param propertyNames * @param declaringNodeTypeNames */ - public static void createIndexDefinition(@Nonnull - NodeUtil indexNode, @Nonnull - String indexDefName, boolean unique, @Nonnull - String[] propertyNames, @Nullable - String[] declaringNodeTypeNames) throws RepositoryException { + public static void createIndexDefinition(@Nonnull NodeUtil indexNode, + @Nonnull String indexDefName, + boolean unique, + @Nonnull String[] propertyNames, + @Nullable String[] declaringNodeTypeNames) throws RepositoryException { - createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, - PropertyIndexEditorProvider.TYPE); + createIndexDefinition(indexNode, indexDefName, unique, propertyNames, declaringNodeTypeNames, PropertyIndexEditorProvider.TYPE); } /** - * Create a new property index definition below the given {@code indexNode} of the provided - * {@code propertyIndexType}. + * Create a new property index definition below the given {@code indexNode} of the provided {@code propertyIndexType}. * * @param indexNode * @param indexDefName @@ -127,12 +118,12 @@ public class IndexUtils { * @param propertyIndexType * @throws RepositoryException */ - public static void createIndexDefinition(@Nonnull - NodeUtil indexNode, @Nonnull - String indexDefName, boolean unique, @Nonnull - String[] propertyNames, @Nullable - String[] declaringNodeTypeNames, @Nonnull - String propertyIndexType) throws RepositoryException { + public static void createIndexDefinition(@Nonnull NodeUtil indexNode, + @Nonnull String indexDefName, + boolean unique, + @Nonnull String[] propertyNames, + @Nullable String[] declaringNodeTypeNames, + @Nonnull String propertyIndexType) throws RepositoryException { NodeUtil entry = indexNode.getOrAddChild(indexDefName, INDEX_DEFINITIONS_NODE_TYPE); entry.setString(TYPE_PROPERTY_NAME, propertyIndexType); entry.setBoolean(REINDEX_PROPERTY_NAME, true); @@ -147,8 +138,7 @@ public class IndexUtils { public static void createReferenceIndex(@Nonnull NodeBuilder index) { - index.child(NodeReferenceConstants.NAME).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); + index.child(NodeReferenceConstants.NAME).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME).setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); } public static boolean isIndexNodeType(NodeState state) { -- 1.8.3.4 (Apple Git-47) From 1c5627a1c897211ced21147d9c3c27e6a4d4fb12 Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Mon, 3 Mar 2014 12:56:35 +0100 Subject: [PATCH 5/7] OAK-1263 fixing formatting --- .../jackrabbit/oak/plugins/index/IndexUtils.java | 18 ++++++++++++------ .../oak/plugins/index/property/PropertyIndex.java | 11 ++++------- 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java index bcc394f..25cc7c9 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java @@ -78,7 +78,10 @@ public class IndexUtils { boolean unique, @Nonnull Collection propertyNames, @Nullable Collection declaringNodeTypeNames) { - NodeBuilder entry = index.child(indexDefName).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME).setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE).setProperty(REINDEX_PROPERTY_NAME, reindex); + NodeBuilder entry = index.child(indexDefName) + .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) + .setProperty(REINDEX_PROPERTY_NAME, reindex); if (unique) { entry.setProperty(UNIQUE_PROPERTY_NAME, unique); } @@ -136,14 +139,16 @@ public class IndexUtils { entry.setNames(PROPERTY_NAMES, propertyNames); } - public static void createReferenceIndex(@Nonnull - NodeBuilder index) { - index.child(NodeReferenceConstants.NAME).setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME).setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); + public static void createReferenceIndex(@Nonnull NodeBuilder index) { + index.child(NodeReferenceConstants.NAME) + .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, NodeReferenceConstants.TYPE); } public static boolean isIndexNodeType(NodeState state) { PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); - return ps != null && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); + return ps != null + && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); } public static boolean isIndexNodeType(NodeState state, String typeIn) { @@ -151,7 +156,8 @@ public class IndexUtils { return false; } PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); - return type != null && !type.isArray() && type.getValue(Type.STRING).equals(typeIn); + return type != null && !type.isArray() + && type.getValue(Type.STRING).equals(typeIn); } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java index dd36ba6..bde1035 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java @@ -50,15 +50,13 @@ import com.google.common.collect.Iterables; * Optionally you can specify *
        *
      • a uniqueness constraint on a property index by setting the unique flag to true
      • - *
      • that the property index only applies to a certain node type by setting the declaringNodeTypes - * property
      • + *
      • that the property index only applies to a certain node type by setting the declaringNodeTypes property
      • *
      *

      *

      * Notes: *

        - *
      • propertyNames 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
      • + *
      • propertyNames 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
      • *
      • reindex is a property that when set to true, triggers a full content reindex.
      • *
      *

      @@ -114,7 +112,7 @@ class PropertyIndex implements QueryIndex { return values; } - // --------------------------------------------------------< QueryIndex >-- + //--------------------------------------------------------< QueryIndex >-- @Override public String getIndexName() { @@ -198,8 +196,7 @@ class PropertyIndex implements QueryIndex { } } if (paths == null) { - throw new IllegalStateException("Property index is used even when no index is available for filter " - + filter); + throw new IllegalStateException("Property index is used even when no index is available for filter " + filter); } Cursor c = Cursors.newPathCursor(paths); if (depth > 1) { -- 1.8.3.4 (Apple Git-47) From 74f8a9cd4f647785540487e4ee75c80cfebae76e Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Tue, 4 Mar 2014 14:44:04 +0000 Subject: [PATCH 6/7] OAK-1263 another round of formatting --- .../jackrabbit/oak/plugins/index/IndexUtils.java | 22 +- .../oak/plugins/index/property/OrderedIndex.java | 3 +- .../index/property/OrderedPropertyIndex.java | 6 +- .../index/property/OrderedPropertyIndexEditor.java | 213 ++++++----- .../property/OrderedPropertyIndexProvider.java | 20 +- .../oak/plugins/index/property/PropertyIndex.java | 13 +- .../index/property/PropertyIndexEditor.java | 7 +- .../index/property/PropertyIndexLookup.java | 48 +-- .../strategy/ContentMirrorStoreStrategy.java | 75 ++-- .../OrderedContentMirrorStoreStrategy.java | 421 +++++++++++---------- 10 files changed, 431 insertions(+), 397 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java index 25cc7c9..5e5df85 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/IndexUtils.java @@ -64,11 +64,11 @@ public class IndexUtils { /** * Create a new property index definition below the given {@code indexNode}. * - * @param index The oak:index node builder - * @param indexDefName The name of the new property index. - * @param reindex {@code true} if the the reindex flag should be turned on. - * @param unique {@code true} if the index is expected the assert property uniqueness. - * @param propertyNames The property names that should be indexed. + * @param index The oak:index node builder + * @param indexDefName The name of the new property index. + * @param reindex {@code true} if the the reindex flag should be turned on. + * @param unique {@code true} if the index is expected the assert property uniqueness. + * @param propertyNames The property names that should be indexed. * @param declaringNodeTypeNames The declaring node type names or {@code null}. * @return the NodeBuilder of the new index definition. */ @@ -79,9 +79,9 @@ public class IndexUtils { @Nonnull Collection propertyNames, @Nullable Collection declaringNodeTypeNames) { NodeBuilder entry = index.child(indexDefName) - .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) - .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) - .setProperty(REINDEX_PROPERTY_NAME, reindex); + .setProperty(JCR_PRIMARYTYPE, INDEX_DEFINITIONS_NODE_TYPE, NAME) + .setProperty(TYPE_PROPERTY_NAME, PropertyIndexEditorProvider.TYPE) + .setProperty(REINDEX_PROPERTY_NAME, reindex); if (unique) { entry.setProperty(UNIQUE_PROPERTY_NAME, unique); } @@ -94,7 +94,7 @@ public class IndexUtils { /** * Create a new property2 index definition below the given {@code indexNode}. - * + * * @param indexNode * @param indexDefName * @param unique @@ -147,7 +147,7 @@ public class IndexUtils { public static boolean isIndexNodeType(NodeState state) { PropertyState ps = state.getProperty(JCR_PRIMARYTYPE); - return ps != null + return ps != null && ps.getValue(STRING).equals(INDEX_DEFINITIONS_NODE_TYPE); } @@ -156,7 +156,7 @@ public class IndexUtils { return false; } PropertyState type = state.getProperty(TYPE_PROPERTY_NAME); - return type != null && !type.isArray() + return type != null && !type.isArray() && type.getValue(Type.STRING).equals(typeIn); } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java index 38fa6df..e45a1db 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedIndex.java @@ -18,7 +18,8 @@ package org.apache.jackrabbit.oak.plugins.index.property; /** - * interface for shared constants around different actors: QueryIndex, IndexEditors, IndexEditorProviders, ... + * interface for shared constants around different actors: QueryIndex, IndexEditors, + * IndexEditorProviders, ... */ public interface OrderedIndex { String TYPE = "ordered"; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java index 9a94445..0fb6c35 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndex.java @@ -17,12 +17,10 @@ package org.apache.jackrabbit.oak.plugins.index.property; +import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.TYPE; + import org.apache.jackrabbit.oak.spi.state.NodeState; -import static org.apache.jackrabbit.oak.plugins.index.property.OrderedIndex.*; -/** - * - */ public class OrderedPropertyIndex extends PropertyIndex { @Override diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java index 8a02f2e..36ff888 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditor.java @@ -38,110 +38,113 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; public class OrderedPropertyIndexEditor extends PropertyIndexEditor { - private static final Logger log = LoggerFactory.getLogger(OrderedPropertyIndexEditor.class); - private static final IndexStoreStrategy ORDERED_MIRROR = new OrderedContentMirrorStoreStrategy(); - - private final Set propertyNames; - - private boolean properlyConfigured; - - - public OrderedPropertyIndexEditor(NodeBuilder definition, NodeState root, IndexUpdateCallback callback){ - super(definition,root,callback); - - Set pns = null; - - PropertyState names = definition.getProperty(IndexConstants.PROPERTY_NAMES); - if(names != null){ - String value = names.getValue(Type.NAME,0); - if(Strings.isNullOrEmpty(value)){ - log.warn("Empty value passed as propertyNames. Index not properly configured. Ignoring."); - }else{ - if(names.isArray()){ - log.warn("Only single value supported. '{}' only will be used.", value); + private static final Logger log = LoggerFactory.getLogger(OrderedPropertyIndexEditor.class); + private static final IndexStoreStrategy ORDERED_MIRROR = new OrderedContentMirrorStoreStrategy(); + + private final Set propertyNames; + + private boolean properlyConfigured; + + public OrderedPropertyIndexEditor(NodeBuilder definition, NodeState root, + IndexUpdateCallback callback) { + super(definition, root, callback); + + Set pns = null; + + PropertyState names = definition.getProperty(IndexConstants.PROPERTY_NAMES); + if (names != null) { + String value = names.getValue(Type.NAME, 0); + if (Strings.isNullOrEmpty(value)) { + log.warn("Empty value passed as propertyNames. Index not properly configured. Ignoring."); + } else { + if (names.isArray()) { + log.warn("Only single value supported. '{}' only will be used.", value); + } + pns = Collections.singleton(value); + this.properlyConfigured = true; } - pns = Collections.singleton(value); - this.properlyConfigured=true; - } - } - - this.propertyNames = pns; - } - - OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name) { - super(parent,name); - this.propertyNames = parent.getPropertyNames(); - } - - /** - * Same as {@link PropertyIndexEditor#getStrategy(boolean)} but ignores the boolean flag. - * - * @return the proper index strategy - */ - @Override - IndexStoreStrategy getStrategy(boolean unique) { - return ORDERED_MIRROR; - } - - public boolean isProperlyConfigured() { - return properlyConfigured; - } - - @Override - Set getPropertyNames() { - return propertyNames; - } - - @Override - PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, @Nonnull String name) { - return new OrderedPropertyIndexEditor(this, name); - } - - @Override - public void enter(NodeState before, NodeState after) { - log.debug("enter() - before: {} - after: {}",before, after); - super.enter(before, after); - } - - @Override - public void leave(NodeState before, NodeState after) throws CommitFailedException { - log.debug("leave() - before: {} - after: {}",before,after); - super.leave(before, after); - } - - @Override - public void propertyAdded(PropertyState after) { - log.debug("propertyAdded() - after: {}",after); - super.propertyAdded(after); - } - - @Override - public void propertyChanged(PropertyState before, PropertyState after) { - log.debug("propertyChanged() - before: {} - after: {}",before,after); - super.propertyChanged(before, after); - } - - @Override - public void propertyDeleted(PropertyState before) { - log.debug("propertyDeleted() - before: {}", before); - super.propertyDeleted(before); - } - - @Override - public Editor childNodeAdded(String name, NodeState after) { - log.debug("childNodeAdded() - name: {} - after: {}",name,after); - return super.childNodeAdded(name, after); - } - - @Override - public Editor childNodeChanged(String name, NodeState before, NodeState after) { - log.debug("childNodeChanged() - name: {} - before: {} - after: {}",new Object[]{name,before,after}); - return super.childNodeChanged(name, before, after); - } - - @Override - public Editor childNodeDeleted(String name, NodeState before) { - log.debug("childNodeDeleted() - name: {} - before: {}",name,before); - return super.childNodeDeleted(name, before); - } + } + + this.propertyNames = pns; + } + + OrderedPropertyIndexEditor(OrderedPropertyIndexEditor parent, String name) { + super(parent, name); + this.propertyNames = parent.getPropertyNames(); + } + + /** + * Same as {@link PropertyIndexEditor#getStrategy(boolean)} but ignores the boolean flag. + * + * @return the proper index strategy + */ + @Override + IndexStoreStrategy getStrategy(boolean unique) { + return ORDERED_MIRROR; + } + + public boolean isProperlyConfigured() { + return properlyConfigured; + } + + @Override + Set getPropertyNames() { + return propertyNames; + } + + @Override + PropertyIndexEditor getChildIndexEditor(@Nonnull PropertyIndexEditor parent, + @Nonnull String name) { + return new OrderedPropertyIndexEditor(this, name); + } + + @Override + public void enter(NodeState before, NodeState after) { + log.debug("enter() - before: {} - after: {}", before, after); + super.enter(before, after); + } + + @Override + public void leave(NodeState before, NodeState after) throws CommitFailedException { + log.debug("leave() - before: {} - after: {}", before, after); + super.leave(before, after); + } + + @Override + public void propertyAdded(PropertyState after) { + log.debug("propertyAdded() - after: {}", after); + super.propertyAdded(after); + } + + @Override + public void propertyChanged(PropertyState before, PropertyState after) { + log.debug("propertyChanged() - before: {} - after: {}", before, after); + super.propertyChanged(before, after); + } + + @Override + public void propertyDeleted(PropertyState before) { + log.debug("propertyDeleted() - before: {}", before); + super.propertyDeleted(before); + } + + @Override + public Editor childNodeAdded(String name, NodeState after) { + log.debug("childNodeAdded() - name: {} - after: {}", name, after); + return super.childNodeAdded(name, after); + } + + @Override + public Editor childNodeChanged(String name, NodeState before, NodeState after) { + log.debug("childNodeChanged() - name: {} - before: {} - after: {}", new Object[] { name, + before, + after }); + return super.childNodeChanged(name, before, after); + } + + @Override + public Editor childNodeDeleted(String name, NodeState before) { + log.debug("childNodeDeleted() - name: {} - before: {}", name, before); + return super.childNodeDeleted(name, before); + } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java index 15daaed..755d85a 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java @@ -33,12 +33,16 @@ import com.google.common.collect.ImmutableList; @Service(QueryIndexProvider.class) public class OrderedPropertyIndexProvider implements QueryIndexProvider { - /* (non-Javadoc) - * @see org.apache.jackrabbit.oak.spi.query.QueryIndexProvider#getQueryIndexes(org.apache.jackrabbit.oak.spi.state.NodeState) - */ - @Override - @Nonnull - public List getQueryIndexes(NodeState nodeState) { - return ImmutableList.of(new OrderedPropertyIndex()); - } + /* + * (non-Javadoc) + * + * @see + * org.apache.jackrabbit.oak.spi.query.QueryIndexProvider#getQueryIndexes(org.apache.jackrabbit + * .oak.spi.state.NodeState) + */ + @Override + @Nonnull + public List getQueryIndexes(NodeState nodeState) { + return ImmutableList. of(new OrderedPropertyIndex()); + } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java index bde1035..57b0a72 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java @@ -36,9 +36,10 @@ import com.google.common.collect.Iterables; /** * Provides a QueryIndex that does lookups against a property index - * + * *

      - * To define a property index on a subtree you have to add an oak:index node.
      + * To define a property index on a subtree you have to add an oak:index node. + *
      * Next (as a child node) follows the index definition node that: *

        *
      • must be of type oak:QueryIndexDefinition
      • @@ -123,7 +124,7 @@ class PropertyIndex implements QueryIndex { * return the proper implementation of the Lookup * * @param root - * @return + * @return the lookup */ PropertyIndexLookup getLookup(NodeState root) { return new PropertyIndexLookup(root); @@ -174,7 +175,8 @@ class PropertyIndex implements QueryIndex { // currently, only indexes on the root node are supported if (lookup.isIndexed(propertyName, "/", filter)) { // equality - if (pr.firstIncluding && pr.lastIncluding && pr.first != null && pr.first.equals(pr.last)) { + if (pr.firstIncluding && pr.lastIncluding + && pr.first != null && pr.first.equals(pr.last)) { // "[property] = $value" paths = lookup.query(filter, propertyName, pr.first); break; @@ -215,7 +217,8 @@ class PropertyIndex implements QueryIndex { // TODO support indexes on a path // currently, only indexes on the root node are supported if (lookup.isIndexed(propertyName, "/", filter)) { - if (pr.firstIncluding && pr.lastIncluding && pr.first != null && pr.first.equals(pr.last)) { + if (pr.firstIncluding && pr.lastIncluding + && pr.first != null && pr.first.equals(pr.last)) { buff.append(' ').append(propertyName).append('=').append(pr.first); } else { buff.append(' ').append(propertyName); diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java index 16ea189..1c9ebad 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java @@ -57,6 +57,7 @@ import com.google.common.base.Predicate; * @see PropertyIndexLookup */ class PropertyIndexEditor implements IndexEditor { + /** Index storage strategy */ private static final IndexStoreStrategy MIRROR = new ContentMirrorStoreStrategy(); @@ -79,7 +80,7 @@ class PropertyIndexEditor implements IndexEditor { private final Set propertyNames; - /** Type predicate, or {@code null} if there are no type restrictions */ + /** Type predicate, or {@code null} if there are no type restrictions */ private final Predicate typePredicate; /** @@ -121,7 +122,7 @@ class PropertyIndexEditor implements IndexEditor { } else { this.propertyNames = newHashSet(names.getValue(NAMES)); } - + // get declaring types, and all their subtypes // TODO: should we reindex when type definitions change? if (definition.hasProperty(DECLARING_NODE_TYPES)) { @@ -154,7 +155,7 @@ class PropertyIndexEditor implements IndexEditor { /** * commodity method for allowing extensions * - * @return + * @return the propertyNames */ Set getPropertyNames() { return propertyNames; diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java index a2ae9d1..ecf9114 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java @@ -42,8 +42,10 @@ import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; /** - * Is responsible for querying the property index content.
        - * This class can be used directly on a subtree where there is an index defined by supplying a {@link NodeState} root. + * Is responsible for querying the property index content. + *
        + * This class can be used directly on a subtree where there is an index defined + * by supplying a {@link NodeState} root. * *
          * 
        @@ -61,17 +63,19 @@ public class PropertyIndexLookup {
              * The cost overhead to use the index in number of read operations.
              */
             private static final int COST_OVERHEAD = 2;
        -
        +    
             /**
              * The maximum cost when the index can be used.
              */
             private static final int MAX_COST = 100;
         
             /** Index storage strategy */
        -    private static final IndexStoreStrategy MIRROR = new ContentMirrorStoreStrategy();
        +    private static final IndexStoreStrategy MIRROR =
        +            new ContentMirrorStoreStrategy();
         
             /** Index storage strategy */
        -    private static final IndexStoreStrategy UNIQUE = new UniqueEntryStoreStrategy();
        +    private static final IndexStoreStrategy UNIQUE =
        +            new UniqueEntryStoreStrategy();
         
             private final NodeState root;
         
        @@ -80,15 +84,13 @@ public class PropertyIndexLookup {
             }
         
             /**
        -     * 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.
        +     * 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 propertyName
        -     *            property name
        -     * @param path
        -     *            lookup path
        -     * @param filter
        -     *            for the node type restriction (null if no node type restriction)
        +     * @param propertyName property name
        +     * @param path lookup path
        +     * @param filter for the node type restriction (null if no node type restriction)
              * @return true if the property is indexed
              */
             public boolean isIndexed(String propertyName, String path, Filter filter) {
        @@ -126,18 +128,20 @@ public class PropertyIndexLookup {
                 if (indexMeta == null) {
                     return Double.POSITIVE_INFINITY;
                 }
        -        return COST_OVERHEAD + getStrategy(indexMeta).count(indexMeta, encode(value), MAX_COST);
        +        return COST_OVERHEAD +
        +                getStrategy(indexMeta).count(indexMeta, encode(value), MAX_COST);
             }
         
             /**
        -     * Get the node with the index definition for the given property, if there is an applicable index with data.
        +     * Get the node with the index definition for the given property, if there
        +     * is an applicable index with data.
              * 
        -     * @param propertyName
        -     *            the property name
        -     * @param filter
        -     *            the filter (which contains information of all supertypes, unless the filter matches all types)
        -     * @return the node where the index definition (metadata) is stored (the parent of ":index"), or null if no index
        -     *         definition or index data node was found
        +     * @param propertyName the property name
        +     * @param filter the filter (which contains information of all supertypes,
        +     *            unless the filter matches all types)
        +     * @return the node where the index definition (metadata) is stored (the
        +     *         parent of ":index"), or null if no index definition or index data
        +     *         node was found
              */
             @Nullable
             private NodeState getIndexNode(NodeState node, String propertyName, Filter filter) {
        @@ -181,7 +185,7 @@ public class PropertyIndexLookup {
             /**
              * retrieve the type of the index
              * 
        -     * @return
        +     * @return the type
              */
             String getType() {
                 return TYPE;
        diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
        index 9cc2a84..89a8532 100644
        --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
        +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java
        @@ -44,11 +44,13 @@ import com.google.common.collect.Queues;
         import com.google.common.collect.Sets;
         
         /**
        - * An IndexStoreStrategy implementation that saves the nodes under a hierarchy that mirrors the repository tree. 
        - * This should minimize the chance that concurrent updates overlap on the same content node.
        + * An IndexStoreStrategy implementation that saves the nodes under a hierarchy + * that mirrors the repository tree.
        + * This should minimize the chance that concurrent updates overlap on the same + * content node.
        *
        - * For example for a node that is under {@code /test/node}, the index structure will be - * {@code /oak:index/index/test/node}: + * For example for a node that is under {@code /test/node}, the index + * structure will be {@code /oak:index/index/test/node}: * *
          * {@code
        @@ -61,14 +63,16 @@ import com.google.common.collect.Sets;
          *         node
          * }
          * 
        - * + * */ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { static final Logger LOG = LoggerFactory.getLogger(ContentMirrorStoreStrategy.class); @Override - public void update(NodeBuilder index, String path, Set beforeKeys, Set afterKeys) { + public void update( + NodeBuilder index, String path, + Set beforeKeys, Set afterKeys) { for (String key : beforeKeys) { remove(index, key, path); } @@ -90,7 +94,7 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { builders.addFirst(builder); } - // Drop the match value, if present + // Drop the match value, if present if (builder.exists()) { builder.removeProperty("match"); } @@ -127,21 +131,6 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { builder.setProperty("match", true); } - /** - * fetch from the index the key node - * - * @param index - * the current index root - * @param key - * the 'key' to fetch from the repo - * @return the node representing the key - */ - NodeBuilder fetchKeyNode(@Nonnull - NodeBuilder index, @Nonnull - String key) { - return index.child(key); - } - public Iterable query(final Filter filter, final String indexName, final NodeState indexMeta, final String indexStorageNodeName, final Iterable values) { final NodeState index = indexMeta.getChildNode(indexStorageNodeName); @@ -183,7 +172,8 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { return count(indexMeta, INDEX_CONTENT_NODE_NAME, values, max); } - public long count(NodeState indexMeta, final String indexStorageNodeName, Set values, int max) { + public long count(NodeState indexMeta, final String indexStorageNodeName, + Set values, int max) { NodeState index = indexMeta.getChildNode(indexStorageNodeName); int count = 0; if (values == null) { @@ -205,7 +195,7 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { int i = 0; for (String p : values) { if (count > max && i > 3) { - // the total count is extrapolated from the the number + // the total count is extrapolated from the the number // of values counted so far to the total number of values count = count * size / i; break; @@ -226,17 +216,18 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { * An iterator over paths within an index node. */ static class PathIterator implements Iterator { - + private final Filter filter; private final String indexName; - private final Deque> nodeIterators = Queues.newArrayDeque(); + private final Deque> nodeIterators = + Queues.newArrayDeque(); private int readCount; private boolean init; private boolean closed; private String parentPath; private String currentPath; private boolean pathContainsValue; - + /** * Keep the returned path, to avoid returning duplicate entries. */ @@ -298,8 +289,7 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { readCount++; if (readCount % 1000 == 0) { FilterIterators.checkReadLimit(readCount); - LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " - + filter); + LOG.warn("Traversed " + readCount + " nodes using index " + indexName + " with filter " + filter); } NodeState node = entry.getNodeState(); @@ -355,8 +345,8 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { } /** - * A node visitor that counts the number of matching nodes up to a given maximum, in order to estimate the number of - * matches. + * A node visitor that counts the number of matching nodes up to a given + * maximum, in order to estimate the number of matches. */ static class CountingNodeVisitor implements NodeVisitor { @@ -376,7 +366,8 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { int depth; /** - * The sum of the depth of all matching nodes. This value is used to calculate the average depth. + * The sum of the depth of all matching nodes. This value is used to + * calculate the average depth. */ long depthTotal; @@ -412,8 +403,9 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { } /** - * The number of estimated matches. This value might be higher than the number of counted matches, if the - * maximum number of matches has been reached. It is based on the average depth of matches, and the average + * The number of estimated matches. This value might be higher than the + * number of counted matches, if the maximum number of matches has been + * reached. It is based on the average depth of matches, and the average * number of child nodes. * * @return the estimated matches @@ -431,5 +423,18 @@ public class ContentMirrorStoreStrategy implements IndexStoreStrategy { } } - + + /** + * fetch from the index the key node + * + * @param index + * the current index root + * @param key + * the 'key' to fetch from the repo + * @return the node representing the key + */ + NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, + @Nonnull String key) { + return index.child(key); + } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java index 22d7717..5e2b203 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java @@ -36,222 +36,237 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; - public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrategy { - private static final Logger log = LoggerFactory.getLogger(OrderedContentMirrorStoreStrategy.class); - - /** - * the property linking to the next node - */ - public static final String NEXT = ":next"; - - /** - * node that works as root of the index (start point or 0 element) - */ - public static final String START = ":start"; - - /** - * a NodeState used for easy creating of an empty :start - */ - public static final NodeState EMPTY_START_NODE = EmptyNodeState.EMPTY_NODE.builder().setProperty(NEXT, "").getNodeState(); - - @Override - NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) { - log.debug("fetchKeyNode() - index: {} - key: {}",index,key); - NodeBuilder _key = null; - NodeBuilder start = index.child(START); - - //identifying the right place for insert - String n = start.getString(NEXT); - if(Strings.isNullOrEmpty(n)){ - //new/empty index - _key = index.child(key); - _key.setProperty(NEXT,""); - start.setProperty(NEXT, key); - }else{ - //specific use-case where the item has to be added as first of the list - String nextKey = n; - if(key.compareTo(nextKey)<0){ + private static final Logger log = LoggerFactory.getLogger(OrderedContentMirrorStoreStrategy.class); + + /** + * the property linking to the next node + */ + public static final String NEXT = ":next"; + + /** + * node that works as root of the index (start point or 0 element) + */ + public static final String START = ":start"; + + /** + * a NodeState used for easy creating of an empty :start + */ + public static final NodeState EMPTY_START_NODE = EmptyNodeState.EMPTY_NODE.builder() + .setProperty(NEXT, "") + .getNodeState(); + + @Override + NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) { + log.debug("fetchKeyNode() - index: {} - key: {}", index, key); + NodeBuilder _key = null; + NodeBuilder start = index.child(START); + + // identifying the right place for insert + String n = start.getString(NEXT); + if (Strings.isNullOrEmpty(n)) { + // new/empty index _key = index.child(key); - _key.setProperty(NEXT,nextKey); + _key.setProperty(NEXT, ""); start.setProperty(NEXT, key); - }else{ - Iterable children = getChildNodeEntries(index.getNodeState()); - for(ChildNodeEntry child : children){ - nextKey = child.getNodeState().getString(NEXT); - if(Strings.isNullOrEmpty(nextKey)){ - //we're at the last element, therefore our 'key' has to be appended - index.getChildNode(child.getName()).setProperty(NEXT, key); - _key = index.child(key); - _key.setProperty(NEXT, ""); - } else { - if(key.compareTo(nextKey)<0){ - index.getChildNode(child.getName()).setProperty(NEXT, key); - _key = index.child(key); - _key.setProperty(NEXT, nextKey); - break; - } - } + } else { + // specific use-case where the item has to be added as first of the list + String nextKey = n; + if (key.compareTo(nextKey) < 0) { + _key = index.child(key); + _key.setProperty(NEXT, nextKey); + start.setProperty(NEXT, key); + } else { + Iterable children = getChildNodeEntries(index.getNodeState()); + for (ChildNodeEntry child : children) { + nextKey = child.getNodeState().getString(NEXT); + if (Strings.isNullOrEmpty(nextKey)) { + // we're at the last element, therefore our 'key' has to be appended + index.getChildNode(child.getName()).setProperty(NEXT, key); + _key = index.child(key); + _key.setProperty(NEXT, ""); + } else { + if (key.compareTo(nextKey) < 0) { + index.getChildNode(child.getName()).setProperty(NEXT, key); + _key = index.child(key); + _key.setProperty(NEXT, nextKey); + break; + } + } + } } - } - } - - return _key; - } + } + + return _key; + } - @Override - void prune(final NodeBuilder index, final Deque builders){ - for(NodeBuilder node : builders){ - if(node.hasProperty("match") || node.getChildNodeCount(1) > 0){ - return; - }else if (node.exists()) { - if(node.hasProperty(NEXT)){ - //it's an index key and we have to relink the list - ChildNodeEntry previous = findPrevious(index.getNodeState(), node.getNodeState()); //(1) find the previous element - log.debug("previous: {}",previous); - String next = node.getString(NEXT); //(2) find the next element - if(next==null) { - next = ""; - } - index.getChildNode(previous.getName()).setProperty(NEXT, next); //(3) re-link the previous to the next - node.remove(); //(4) remove the current node - }else{ - node.remove(); + @Override + void prune(final NodeBuilder index, final Deque builders) { + for (NodeBuilder node : builders) { + if (node.hasProperty("match") || node.getChildNodeCount(1) > 0) { + return; + } else if (node.exists()) { + if (node.hasProperty(NEXT)) { + // it's an index key and we have to relink the list + ChildNodeEntry previous = findPrevious(index.getNodeState(), + node.getNodeState()); // (1) find the + // previous element + log.debug("previous: {}", previous); + String next = node.getString(NEXT); // (2) find the next element + if (next == null) { + next = ""; + } + index.getChildNode(previous.getName()).setProperty(NEXT, next); // (3) re-link + // the previous + // to the next + node.remove(); // (4) remove the current node + } else { + node.remove(); + } } - } - } - } + } + } - @Nullable ChildNodeEntry findPrevious(@Nonnull final NodeState index, @Nonnull final NodeState node){ - ChildNodeEntry previous = null; - ChildNodeEntry current = null; - boolean found = false; - Iterator it = getChildNodeEntries(index,true).iterator(); - - while(!found && it.hasNext()){ - current = it.next(); - if(previous==null){ - //first iteration - previous = current; - }else{ - found = node.equals(current.getNodeState()); - if(!found) { + @Nullable + ChildNodeEntry findPrevious(@Nonnull final NodeState index, @Nonnull final NodeState node) { + ChildNodeEntry previous = null; + ChildNodeEntry current = null; + boolean found = false; + Iterator it = getChildNodeEntries(index, true).iterator(); + + while (!found && it.hasNext()) { + current = it.next(); + if (previous == null) { + // first iteration previous = current; + } else { + found = node.equals(current.getNodeState()); + if (!found) { + previous = current; + } } - } - } - - return ((found) ? previous : null); - } - - @Override - public void update(NodeBuilder index, String path, Set beforeKeys, Set afterKeys) { - log.debug("update() - index : {}",index); - log.debug("update() - path : {}",path); - log.debug("update() - beforeKeys: {}",beforeKeys); - log.debug("update() - afterKeys : {}",afterKeys); - super.update(index, path, beforeKeys, afterKeys); - } - - - /** - * retrieve an Iterable for going through the index in the right order without the :start node - * - * @param index the root of the index (:index) - * @return - */ - @Override - @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index){ - return getChildNodeEntries(index, false); - } - - /** - * Retrieve an Iterable for going through the index in the right order with potentially the :start node - * - * @param index the root of the index (:index) - * @param includeStart true if :start should be included as first element - * @return - */ - @Nonnull Iterable getChildNodeEntries(@Nonnull final NodeState index, final boolean includeStart){ - Iterable cne = null; - final NodeState start = index.getChildNode(START); - - if((!start.exists() || Strings.isNullOrEmpty(start.getString(NEXT))) && !includeStart) { - //if the property is not there or is empty it means we're empty - cne = Collections.emptyList(); - }else{ - cne = new Iterable(){ - private NodeState _index = index; - private NodeState _start = ((includeStart && !start.exists())?EMPTY_START_NODE:start); - private NodeState current = _start; - private boolean _includeStart = includeStart; + } + + return ((found) ? previous : null); + } - @Override - public Iterator iterator() { - return new Iterator(){ + @Override + public void update(NodeBuilder index, String path, Set beforeKeys, + Set afterKeys) { + log.debug("update() - index : {}", index); + log.debug("update() - path : {}", path); + log.debug("update() - beforeKeys: {}", beforeKeys); + log.debug("update() - afterKeys : {}", afterKeys); + super.update(index, path, beforeKeys, afterKeys); + } - @Override - public boolean hasNext() { - return ( - (_includeStart && _start.equals(current)) || - (!_includeStart && !Strings.isNullOrEmpty(current.getString(NEXT))) - ); - } + /** + * retrieve an Iterable for going through the index in the right order without the :start node + * + * @param index the root of the index (:index) + * @return + */ + @Override + @Nonnull + Iterable getChildNodeEntries(@Nonnull final NodeState index) { + return getChildNodeEntries(index, false); + } - @Override - public ChildNodeEntry next() { - ChildNodeEntry _cne = null; - if(_includeStart && _start.equals(current)){ - _cne = new OrderedChildNodeEntry(START, current); - _includeStart = false; //let's set it to false. We just included it. - } else { - if(hasNext()){ - final String name = current.getString(NEXT); - current = _index.getChildNode(name); - _cne = new OrderedChildNodeEntry(name, current); - }else{ - throw new NoSuchElementException(); + /** + * Retrieve an Iterable for going through the index in the right order with potentially the + * :start node + * + * @param index the root of the index (:index) + * @param includeStart true if :start should be included as first element + * @return + */ + @Nonnull + Iterable getChildNodeEntries(@Nonnull final NodeState index, + final boolean includeStart) { + Iterable cne = null; + final NodeState start = index.getChildNode(START); + + if ((!start.exists() || Strings.isNullOrEmpty(start.getString(NEXT))) && !includeStart) { + // if the property is not there or is empty it means we're empty + cne = Collections.emptyList(); + } else { + cne = new Iterable() { + private NodeState _index = index; + private NodeState _start = ((includeStart && !start.exists()) ? EMPTY_START_NODE + : start); + private NodeState current = _start; + private boolean _includeStart = includeStart; + + @Override + public Iterator iterator() { + return new Iterator() { + + @Override + public boolean hasNext() { + return ((_includeStart && _start.equals(current)) || (!_includeStart && !Strings.isNullOrEmpty(current.getString(NEXT)))); } - } - return _cne; - } - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - }; - } - return cne; - } - - private static final class OrderedChildNodeEntry extends AbstractChildNodeEntry { - private final String name; - private final NodeState state; - - public OrderedChildNodeEntry(@Nonnull final String name,@Nonnull final NodeState state){ - this.name = name; - this.state = state; - } - - /* (non-Javadoc) - * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getName() - */ - @Override - @Nonnull - public String getName() { - return name; - } + @Override + public ChildNodeEntry next() { + ChildNodeEntry _cne = null; + if (_includeStart && _start.equals(current)) { + _cne = new OrderedChildNodeEntry(START, current); + _includeStart = false; // let's set it to false. We just included + // it. + } else { + if (hasNext()) { + final String name = current.getString(NEXT); + current = _index.getChildNode(name); + _cne = new OrderedChildNodeEntry(name, current); + } else { + throw new NoSuchElementException(); + } + } + return _cne; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + }; + } + return cne; + } + + private static final class OrderedChildNodeEntry extends AbstractChildNodeEntry { + private final String name; + private final NodeState state; + + public OrderedChildNodeEntry(@Nonnull + final String name, @Nonnull + final NodeState state) { + this.name = name; + this.state = state; + } + + /* + * (non-Javadoc) + * + * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getName() + */ + @Override + @Nonnull + public String getName() { + return name; + } - /* (non-Javadoc) - * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getNodeState() - */ - @Override - @Nonnull - public NodeState getNodeState() { - return state; - } - } + /* + * (non-Javadoc) + * + * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getNodeState() + */ + @Override + @Nonnull + public NodeState getNodeState() { + return state; + } + } } -- 1.8.3.4 (Apple Git-47) From 035c3654cbf3c731c43c6c193ff74bc2055e27dd Mon Sep 17 00:00:00 2001 From: Davide Giannella Date: Tue, 4 Mar 2014 15:15:59 +0000 Subject: [PATCH 7/7] OAK-1263 another round of formatting --- .../OrderedPropertyIndexEditorProvider.java | 4 +- .../property/OrderedPropertyIndexProvider.java | 7 ---- .../OrderedContentMirrorStoreStrategy.java | 44 ++++++++++++---------- .../OrderedContentMirrorStorageStrategyTest.java | 21 ++--------- 4 files changed, 29 insertions(+), 47 deletions(-) diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java index 3ea60ae..c91daa4 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexEditorProvider.java @@ -36,7 +36,7 @@ public class OrderedPropertyIndexEditorProvider implements IndexEditorProvider, @Override @CheckForNull public Editor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, @Nonnull NodeState root, @Nonnull IndexUpdateCallback callback) throws CommitFailedException { - Editor _editor = (TYPE.equals(type)) ? new OrderedPropertyIndexEditor(definition,root,callback) : null; - return _editor; + Editor editor = (TYPE.equals(type)) ? new OrderedPropertyIndexEditor(definition,root,callback) : null; + return editor; } } diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java index 755d85a..ab6d137 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/OrderedPropertyIndexProvider.java @@ -33,13 +33,6 @@ import com.google.common.collect.ImmutableList; @Service(QueryIndexProvider.class) public class OrderedPropertyIndexProvider implements QueryIndexProvider { - /* - * (non-Javadoc) - * - * @see - * org.apache.jackrabbit.oak.spi.query.QueryIndexProvider#getQueryIndexes(org.apache.jackrabbit - * .oak.spi.state.NodeState) - */ @Override @Nonnull public List getQueryIndexes(NodeState nodeState) { diff --git a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java index 5e2b203..a5e4e67 100644 --- a/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java +++ b/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStoreStrategy.java @@ -36,6 +36,20 @@ import org.slf4j.LoggerFactory; import com.google.common.base.Strings; +/** + * Same as for {@link ContentMirrorStoreStrategy} but the order of the keys is kept by using the + * following structure + * + * + * :index : { + * :start : { :next = n1 }, + * n0 : { /content/foo/bar(match=true), :next=n3 }, + * n1 : { /content/foo1/bar(match=true), :next=n0 }, + * n2 : { /content/foo2/bar(match=true), :next= }, //this is the end of the list + * n3 : { /content/foo3/bar(match=true), :next=n2 } + * } + * + */ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrategy { private static final Logger log = LoggerFactory.getLogger(OrderedContentMirrorStoreStrategy.class); @@ -59,22 +73,22 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg @Override NodeBuilder fetchKeyNode(@Nonnull NodeBuilder index, @Nonnull String key) { log.debug("fetchKeyNode() - index: {} - key: {}", index, key); - NodeBuilder _key = null; + NodeBuilder localkey = null; NodeBuilder start = index.child(START); // identifying the right place for insert String n = start.getString(NEXT); if (Strings.isNullOrEmpty(n)) { // new/empty index - _key = index.child(key); - _key.setProperty(NEXT, ""); + localkey = index.child(key); + localkey.setProperty(NEXT, ""); start.setProperty(NEXT, key); } else { // specific use-case where the item has to be added as first of the list String nextKey = n; if (key.compareTo(nextKey) < 0) { - _key = index.child(key); - _key.setProperty(NEXT, nextKey); + localkey = index.child(key); + localkey.setProperty(NEXT, nextKey); start.setProperty(NEXT, key); } else { Iterable children = getChildNodeEntries(index.getNodeState()); @@ -83,13 +97,13 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg if (Strings.isNullOrEmpty(nextKey)) { // we're at the last element, therefore our 'key' has to be appended index.getChildNode(child.getName()).setProperty(NEXT, key); - _key = index.child(key); - _key.setProperty(NEXT, ""); + localkey = index.child(key); + localkey.setProperty(NEXT, ""); } else { if (key.compareTo(nextKey) < 0) { index.getChildNode(child.getName()).setProperty(NEXT, key); - _key = index.child(key); - _key.setProperty(NEXT, nextKey); + localkey = index.child(key); + localkey.setProperty(NEXT, nextKey); break; } } @@ -97,7 +111,7 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg } } - return _key; + return localkey; } @Override @@ -247,22 +261,12 @@ public class OrderedContentMirrorStoreStrategy extends ContentMirrorStoreStrateg this.state = state; } - /* - * (non-Javadoc) - * - * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getName() - */ @Override @Nonnull public String getName() { return name; } - /* - * (non-Javadoc) - * - * @see org.apache.jackrabbit.oak.spi.state.ChildNodeEntry#getNodeState() - */ @Override @Nonnull public NodeState getNodeState() { diff --git a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java index 527edcc..fb4a51d 100644 --- a/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java +++ b/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/OrderedContentMirrorStorageStrategyTest.java @@ -189,12 +189,7 @@ public class OrderedContentMirrorStorageStrategyTest { Iterable children = (Iterable) store.getChildNodeEntries(indexState); assertNotNull("The iterable cannot be null", children); - assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // how - // many - // entries - // do - // we - // have? + assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // ensuring the right sequence ChildNodeEntry entry = null; @@ -242,12 +237,7 @@ public class OrderedContentMirrorStorageStrategyTest { Iterable children = (Iterable) store.getChildNodeEntries(indexState, false); assertNotNull("The iterable cannot be null", children); - assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // how - // many - // entries - // do - // we - // have? + assertEquals("Expecting 2 items in the index", 2, Iterators.size(children.iterator())); // ensuring the right sequence ChildNodeEntry entry = null; @@ -296,12 +286,7 @@ public class OrderedContentMirrorStorageStrategyTest { Iterable children = (Iterable) store.getChildNodeEntries(indexState, true); assertNotNull("The iterable cannot be null", children); - assertEquals("Expecting 3 items in the index", 3, Iterators.size(children.iterator())); // how - // many - // entries - // do - // we - // have? + assertEquals("Expecting 3 items in the index", 3, Iterators.size(children.iterator())); // ensuring the right sequence ChildNodeEntry entry = null; -- 1.8.3.4 (Apple Git-47)