Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/Multiplexers.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/Multiplexers.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/Multiplexers.java (revision 0) @@ -0,0 +1,154 @@ +/* + * 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 com.google.common.collect.Sets.newHashSet; +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME; + +import java.util.HashSet; +import java.util.Set; + +import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.FilteringIndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy; +import org.apache.jackrabbit.oak.spi.mount.Mount; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; + +import com.google.common.base.Predicate; + +public class Multiplexers { + + static boolean RO_PRIVATE_UNIQUE_INDEX; + + static { + String ro = System.getProperty( + "oak.multiplexing.readOnlyPrivateUniqueIndex", "true"); + RO_PRIVATE_UNIQUE_INDEX = Boolean.parseBoolean(ro); + } + + /** Index storage strategy */ + private static final IndexStoreStrategy UNIQUE = new UniqueEntryStoreStrategy( + INDEX_CONTENT_NODE_NAME); + + /** Index storage strategy */ + private static final IndexStoreStrategy MIRROR = new ContentMirrorStoreStrategy( + INDEX_CONTENT_NODE_NAME); + + public static Set getStrategies(boolean unique, + MountInfoProvider mountInfoProvider, NodeBuilder definition, + String defaultName) { + Iterable children = definition.getChildNodeNames(); + return getStrategies(unique, mountInfoProvider, children, defaultName); + } + + public static Set getStrategies(boolean unique, + MountInfoProvider mountInfoProvider, NodeState definition, + String defaultName) { + Iterable children = definition.getChildNodeNames(); + return getStrategies(unique, mountInfoProvider, children, defaultName); + } + + private static Set getStrategies(boolean unique, + MountInfoProvider mountInfoProvider, Iterable children, + String defaultName) { + if (mountInfoProvider.hasNonDefaultMounts()) { + Set names = new HashSet(); + // TODO should this be collected from the index def? + for (String name : children) { + if (isIndexStorageNode(name, defaultName)) { + names.add(name); + } + } + names.remove(defaultName); + Set strategies = new HashSet(); + for (Mount m : mountInfoProvider.getNonDefaultMounts()) { + String n = getNodeForMount(m, defaultName); + names.remove(n); + strategies.add(newStrategy(unique, false, n, m)); + } + + Mount defMount = mountInfoProvider.getDefaultMount(); + // TODO what to do with non-default names that are not covered by + // the mount? + for (String n : names) { + strategies.add(newStrategy(unique, true, n, defMount)); + } + // default mount + strategies.add(newStrategy(unique, true, defaultName, defMount)); + return strategies; + } else { + return unique ? newHashSet(UNIQUE) : newHashSet(MIRROR); + } + } + + private static IndexStoreStrategy newStrategy(boolean unique, + boolean defaultMount, String name, Mount m) { + Predicate filter = newFilter(m); + boolean readOnly = unique && !m.isDefault() && RO_PRIVATE_UNIQUE_INDEX; + return unique ? new FilteringIndexStoreStrategy( + new UniqueEntryStoreStrategy(name), filter, readOnly) + : new FilteringIndexStoreStrategy( + new ContentMirrorStoreStrategy(name), filter); + } + + private static Predicate newFilter(final Mount m) { + return new Predicate() { + + @Override + public boolean apply(String p) { + return m.isMounted(p); + } + }; + } + + private static boolean isIndexStorageNode(String name, String defaultName) { + return NodeStateUtils.isHidden(name) + && (name.equals(defaultName) || name + .endsWith(asSuffix(defaultName))); + } + + public static String getIndexNodeName(MountInfoProvider mountInfoProvider, + String path, String defaultName) { + Mount mount = mountInfoProvider.getMountByPath(path); + return getNodeForMount(mount, defaultName); + } + + public static String getNodeForMount(Mount mount, String defaultName) { + if (mount.isDefault()) { + return defaultName; + } + return ":" + mount.getPathFragmentName() + asSuffix(defaultName); + } + + private static String asSuffix(String name) { + return "-" + stripStartingColon(name); + } + + private static String stripStartingColon(String name) { + if (name.startsWith(":")) { + return name.substring(1); + } + return name; + } + +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndex.java (working copy) @@ -28,6 +28,7 @@ import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.QueryIndex; @@ -98,11 +99,17 @@ private static final Logger LOG = LoggerFactory.getLogger(PropertyIndex.class); + private final MountInfoProvider mountInfoProvider; + /** * Cached property index plan */ private PropertyIndexPlan plan; + PropertyIndex(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + } + static Set encode(PropertyValue value) { if (value == null) { return null; @@ -135,13 +142,14 @@ if (plan != null && plan.getFilter().toString().equals(filter.toString())) { return plan; } else { - plan = createPlan(root, filter); + plan = createPlan(root, filter, mountInfoProvider); this.plan = plan; return plan; } } - private static PropertyIndexPlan createPlan(NodeState root, Filter filter) { + private static PropertyIndexPlan createPlan(NodeState root, Filter filter, + MountInfoProvider mountInfoProvider) { PropertyIndexPlan bestPlan = null; // TODO support indexes on a path @@ -152,7 +160,7 @@ if (PROPERTY.equals(definition.getString(TYPE_PROPERTY_NAME)) && definition.hasChildNode(INDEX_CONTENT_NODE_NAME)) { PropertyIndexPlan plan = new PropertyIndexPlan( - entry.getName(), root, definition, filter); + entry.getName(), root, definition, filter, mountInfoProvider); if (plan.getCost() != Double.POSITIVE_INFINITY) { LOG.debug("property cost for {} is {}", plan.getName(), plan.getCost()); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditor.java (working copy) @@ -41,11 +41,10 @@ import org.apache.jackrabbit.oak.plugins.index.IndexEditor; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; import org.apache.jackrabbit.oak.plugins.index.PathFilter; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy; import org.apache.jackrabbit.oak.plugins.nodetype.TypePredicate; import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; import org.apache.jackrabbit.oak.spi.query.PropertyValues; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -60,14 +59,6 @@ */ class PropertyIndexEditor implements IndexEditor { - /** Index storage strategy */ - private static final IndexStoreStrategy MIRROR = - new ContentMirrorStoreStrategy(); - - /** Index storage strategy */ - private static final IndexStoreStrategy UNIQUE = - new UniqueEntryStoreStrategy(); - /** Parent editor, or {@code null} if this is the root editor. */ private final PropertyIndexEditor parent; @@ -115,8 +106,10 @@ private final PathFilter.Result pathFilterResult; + private final MountInfoProvider mountInfoProvider; + public PropertyIndexEditor(NodeBuilder definition, NodeState root, - IndexUpdateCallback updateCallback) { + IndexUpdateCallback updateCallback, MountInfoProvider mountInfoProvider) { this.parent = null; this.name = null; this.path = "/"; @@ -152,6 +145,7 @@ this.keysToCheckForUniqueness = null; } this.updateCallback = updateCallback; + this.mountInfoProvider = mountInfoProvider; } PropertyIndexEditor(PropertyIndexEditor parent, String name, PathFilter.Result pathFilterResult) { @@ -166,6 +160,7 @@ this.updateCallback = parent.updateCallback; this.pathFilter = parent.pathFilter; this.pathFilterResult = pathFilterResult; + this.mountInfoProvider = parent.mountInfoProvider; } /** @@ -221,8 +216,9 @@ return keys; } - IndexStoreStrategy getStrategy(boolean unique) { - return unique ? UNIQUE : MIRROR; + Set getStrategies(boolean unique) { + return Multiplexers.getStrategies(unique, mountInfoProvider, + definition, INDEX_CONTENT_NODE_NAME); } @Override @@ -285,15 +281,18 @@ if (!beforeKeys.isEmpty() || !afterKeys.isEmpty()) { updateCallback.indexUpdate(); - NodeBuilder index = definition.child(INDEX_CONTENT_NODE_NAME); String properties = definition.getString(PROPERTY_NAMES); boolean uniqueIndex = keysToCheckForUniqueness != null; - if (uniqueIndex) { - keysToCheckForUniqueness.addAll( - getExistingKeys(afterKeys, index)); + for (IndexStoreStrategy strategy : getStrategies(uniqueIndex)) { + NodeBuilder index = definition.child(strategy + .getIndexNodeName()); + if (uniqueIndex) { + keysToCheckForUniqueness.addAll(getExistingKeys( + afterKeys, index, strategy)); + } + strategy.update(index, getPath(), properties, definition, + beforeKeys, afterKeys); } - getStrategy(uniqueIndex).update( - index, getPath(), properties, definition, beforeKeys, afterKeys); } } @@ -328,11 +327,11 @@ * * @param keys the keys * @param index the index + * @param s the index store strategy * @return the set of keys that already exist in this unique index */ - private Set getExistingKeys(Set keys, NodeBuilder index) { + private Set getExistingKeys(Set keys, NodeBuilder index, IndexStoreStrategy s) { Set existing = null; - IndexStoreStrategy s = getStrategy(true); for (String key : keys) { if (s.exists(index, key)) { if (existing == null) { @@ -346,7 +345,7 @@ } return existing; } - + /** * From a set of keys, get the first that has multiple entries, if any. * @@ -355,10 +354,13 @@ * @return the first duplicate, or null if none was found */ private String getFirstDuplicate(Set keys, NodeState indexMeta) { - IndexStoreStrategy s = getStrategy(true); for (String key : keys) { - if (s.count(root, indexMeta, singleton(key), 2) > 1) { - return key; + long count = 0; + for (IndexStoreStrategy s : getStrategies(true)) { + count += s.count(root, indexMeta, singleton(key), 2); + if (count > 1) { + return key; + } } } return null; Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexEditorProvider.java (working copy) @@ -20,11 +20,14 @@ import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.jackrabbit.oak.plugins.index.IndexConstants; import org.apache.jackrabbit.oak.plugins.index.IndexEditorProvider; import org.apache.jackrabbit.oak.spi.commit.Editor; import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -42,13 +45,20 @@ public static final String TYPE = "property"; + @Reference + private MountInfoProvider mountInfoProvider = Mounts.defaultMountInfoProvider(); + @Override public Editor getIndexEditor( @Nonnull String type, @Nonnull NodeBuilder definition, @Nonnull NodeState root, @Nonnull IndexUpdateCallback callback) { if (TYPE.equals(type)) { - return new PropertyIndexEditor(definition, root, callback); + return new PropertyIndexEditor(definition, root, callback, mountInfoProvider); } return null; } + public PropertyIndexEditorProvider with(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + return this; + } } \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexLookup.java (working copy) @@ -25,6 +25,7 @@ import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider.TYPE; import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode; +import java.util.List; import java.util.Set; import javax.annotation.CheckForNull; @@ -35,13 +36,16 @@ import org.apache.jackrabbit.oak.api.Type; import org.apache.jackrabbit.oak.commons.PathUtils; import org.apache.jackrabbit.oak.plugins.index.IndexConstants; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry; import org.apache.jackrabbit.oak.spi.state.NodeState; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + /** * Is responsible for querying the property index content. *
@@ -68,18 +72,17 @@ */ static final int MAX_COST = 100; - /** Index storage strategy */ - private static final IndexStoreStrategy MIRROR = - new ContentMirrorStoreStrategy(); - - /** Index storage strategy */ - private static final IndexStoreStrategy UNIQUE = - new UniqueEntryStoreStrategy(); - private final NodeState root; + private final MountInfoProvider mountInfoProvider; + public PropertyIndexLookup(NodeState root) { + this(root, Mounts.defaultMountInfoProvider()); + } + + public PropertyIndexLookup(NodeState root, MountInfoProvider mountInfoProvider) { this.root = root; + this.mountInfoProvider = mountInfoProvider; } /** @@ -107,19 +110,25 @@ return false; } - public Iterable query(Filter filter, String propertyName, PropertyValue value) { + public Iterable query(Filter filter, String propertyName, + PropertyValue value) { NodeState indexMeta = getIndexNode(root, propertyName, filter); if (indexMeta == null) { throw new IllegalArgumentException("No index for " + propertyName); } - return getStrategy(indexMeta).query(filter, propertyName, indexMeta, encode(value)); + List> iterables = Lists.newArrayList(); + for (IndexStoreStrategy s : getStrategies(indexMeta)) { + iterables.add(s.query(filter, propertyName, indexMeta, + encode(value))); + } + return Iterables.concat(iterables); } - IndexStoreStrategy getStrategy(NodeState indexMeta) { - if (indexMeta.getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME)) { - return UNIQUE; - } - return MIRROR; + Set getStrategies(NodeState definition) { + boolean unique = definition + .getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME); + return Multiplexers.getStrategies(unique, mountInfoProvider, + definition, INDEX_CONTENT_NODE_NAME); } public double getCost(Filter filter, String propertyName, PropertyValue value) { @@ -127,8 +136,12 @@ if (indexMeta == null) { return Double.POSITIVE_INFINITY; } - return COST_OVERHEAD + - getStrategy(indexMeta).count(filter, root, indexMeta, encode(value), MAX_COST); + Set strategies = getStrategies(indexMeta); + double cost = strategies.isEmpty() ? MAX_COST : COST_OVERHEAD; + for (IndexStoreStrategy s : strategies) { + cost += s.count(filter, root, indexMeta, encode(value), MAX_COST); + } + return cost; } /** Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexPlan.java (working copy) @@ -22,25 +22,30 @@ import static com.google.common.collect.Sets.newLinkedHashSet; import static java.util.Collections.emptySet; 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.PROPERTY_NAMES; -import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.UNIQUE_PROPERTY_NAME; import static org.apache.jackrabbit.oak.plugins.index.property.PropertyIndex.encode; +import java.util.List; import java.util.Set; import org.apache.jackrabbit.oak.api.PropertyValue; import org.apache.jackrabbit.oak.commons.PathUtils; +import org.apache.jackrabbit.oak.plugins.index.IndexConstants; import org.apache.jackrabbit.oak.plugins.index.PathFilter; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy; import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.apache.jackrabbit.oak.spi.query.Cursors; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; import org.apache.jackrabbit.oak.spi.state.NodeState; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; + /** * Plan for querying a given property index using a given filter. */ @@ -56,21 +61,13 @@ */ static final int MAX_COST = 100; - /** Index storage strategy */ - private static final IndexStoreStrategy MIRROR = - new ContentMirrorStoreStrategy(); - - /** Index storage strategy */ - private static final IndexStoreStrategy UNIQUE = - new UniqueEntryStoreStrategy(); - private final NodeState definition; private final String name; private final Set properties; - private final IndexStoreStrategy strategy; + private final Set strategies; private final Filter filter; @@ -86,18 +83,18 @@ private final PathFilter pathFilter; - PropertyIndexPlan(String name, NodeState root, NodeState definition, Filter filter) { + PropertyIndexPlan(String name, NodeState root, NodeState definition, + Filter filter){ + this(name, root, definition, filter, Mounts.defaultMountInfoProvider()); + } + + PropertyIndexPlan(String name, NodeState root, NodeState definition, + Filter filter, MountInfoProvider mountInfoProvider) { this.name = name; this.definition = definition; this.properties = newHashSet(definition.getNames(PROPERTY_NAMES)); pathFilter = PathFilter.from(definition.builder()); - - if (definition.getBoolean(UNIQUE_PROPERTY_NAME)) { - this.strategy = UNIQUE; - } else { - this.strategy = MIRROR; - } - + this.strategies = getStrategies(definition, mountInfoProvider); this.filter = filter; Iterable types = definition.getNames(DECLARING_NODE_TYPES); @@ -143,7 +140,11 @@ continue; } Set values = getValues(restriction); - double cost = strategy.count(filter, root, definition, values, MAX_COST); + double cost = strategies.isEmpty() ? MAX_COST : 0; + for (IndexStoreStrategy strategy : strategies) { + cost += strategy.count(filter, root, definition, + values, MAX_COST); + } if (cost < bestCost) { bestDepth = depth; bestValues = values; @@ -188,8 +189,11 @@ Cursor execute() { QueryEngineSettings settings = filter.getQueryEngineSettings(); - Cursor cursor = Cursors.newPathCursor( - strategy.query(filter, name, definition, values), + List> iterables = Lists.newArrayList(); + for (IndexStoreStrategy s : strategies) { + iterables.add(s.query(filter, name, definition, values)); + } + Cursor cursor = Cursors.newPathCursor(Iterables.concat(iterables), settings); if (depth > 1) { cursor = Cursors.newAncestorCursor(cursor, depth - 1, settings); @@ -201,6 +205,14 @@ return filter; } + Set getStrategies(NodeState definition, + MountInfoProvider mountInfoProvider) { + boolean unique = definition + .getBoolean(IndexConstants.UNIQUE_PROPERTY_NAME); + return Multiplexers.getStrategies(unique, mountInfoProvider, + definition, INDEX_CONTENT_NODE_NAME); + } + //------------------------------------------------------------< Object >-- @Override Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexProvider.java (working copy) @@ -21,7 +21,10 @@ import javax.annotation.Nonnull; import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -40,8 +43,16 @@ @Service(QueryIndexProvider.class) public class PropertyIndexProvider implements QueryIndexProvider { + @Reference + private MountInfoProvider mountInfoProvider = Mounts.defaultMountInfoProvider(); + @Override @Nonnull public List getQueryIndexes(NodeState state) { - return ImmutableList.of(new PropertyIndex()); + return ImmutableList.of(new PropertyIndex(mountInfoProvider)); + } + + public PropertyIndexProvider with(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + return this; } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategy.java (working copy) @@ -80,6 +80,16 @@ */ public static final int TRAVERSING_WARN = Integer.getInteger("oak.traversing.warn", 10000); + private final String indexName; + + public ContentMirrorStoreStrategy() { + this(INDEX_CONTENT_NODE_NAME); + } + + public ContentMirrorStoreStrategy(String indexName) { + this.indexName = indexName; + } + @Override public void update( NodeBuilder index, String path, @@ -163,27 +173,22 @@ } @Override - public Iterable query(final Filter filter, final String indexName, + public Iterable query(final Filter filter, final String name, final NodeState indexMeta, final Iterable values) { - return query(filter, indexName, indexMeta, INDEX_CONTENT_NODE_NAME, values); + return query(filter, name, indexMeta, this.indexName, values); } @Override public long count(NodeState root, NodeState indexMeta, Set values, int max) { - return count(root, indexMeta, INDEX_CONTENT_NODE_NAME, values, max); + return count(null, root, indexMeta, this.indexName, values, max); } @Override public long count(final Filter filter, NodeState root, NodeState indexMeta, Set values, int max) { - return count(filter, root, indexMeta, INDEX_CONTENT_NODE_NAME, values, max); + return count(filter, root, indexMeta, this.indexName, values, max); } - public long count(NodeState root, NodeState indexMeta, final String indexStorageNodeName, - Set values, int max) { - return count(null, root, indexMeta, indexStorageNodeName, values, max); - } - - public long count(Filter filter, NodeState root, NodeState indexMeta, final String indexStorageNodeName, + long count(Filter filter, NodeState root, NodeState indexMeta, final String indexStorageNodeName, Set values, int max) { NodeState index = indexMeta.getChildNode(indexStorageNodeName); long count = -1; @@ -586,4 +591,8 @@ throw new UnsupportedOperationException(); } + @Override + public String getIndexNodeName() { + return indexName; + } } \ No newline at end of file Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/FilteringIndexStoreStrategy.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/FilteringIndexStoreStrategy.java (revision 0) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/FilteringIndexStoreStrategy.java (revision 0) @@ -0,0 +1,96 @@ +/* + * 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.Set; + +import org.apache.jackrabbit.oak.api.CommitFailedException; +import org.apache.jackrabbit.oak.spi.query.Filter; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; + +import com.google.common.base.Predicate; + +/** + * A delegating IndexStoreStrategy that filters out updates that are not + * accepted by the given predicate + */ +public class FilteringIndexStoreStrategy implements IndexStoreStrategy { + + private final IndexStoreStrategy strategy; + private final Predicate filter; + private final boolean readOnly; + + public FilteringIndexStoreStrategy(IndexStoreStrategy strategy, + Predicate filter) { + this(strategy, filter, false); + } + + public FilteringIndexStoreStrategy(IndexStoreStrategy strategy, + Predicate filter, boolean readOnly) { + this.strategy = strategy; + this.filter = filter; + this.readOnly = readOnly; + } + + @Override + public void update(NodeBuilder index, String path, String indexName, + NodeBuilder indexMeta, Set beforeKeys, Set afterKeys) + throws CommitFailedException { + if (filter.apply(path)) { + if (readOnly) { + throw new CommitFailedException( + CommitFailedException.UNSUPPORTED, 0, + "Unsupported commit to a read-only store!", + new Throwable("Commit path: " + path)); + } + strategy.update(index, path, indexName, indexMeta, beforeKeys, + afterKeys); + } + } + + @Override + public boolean exists(NodeBuilder index, String key) { + return strategy.exists(index, key); + } + + @Override + public Iterable query(Filter filter, String indexName, + NodeState indexMeta, Iterable values) { + return strategy.query(filter, indexName, indexMeta, values); + } + + @Override + public long count(NodeState root, NodeState indexMeta, Set values, + int max) { + return strategy.count(root, indexMeta, values, max); + } + + @Override + public long count(Filter filter, NodeState root, NodeState indexMeta, + Set values, int max) { + return strategy.count(filter, root, indexMeta, values, max); + } + + @Override + public String getIndexNodeName() { + return strategy.getIndexNodeName(); + } + +} Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/IndexStoreStrategy.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/IndexStoreStrategy.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/IndexStoreStrategy.java (working copy) @@ -18,6 +18,7 @@ import java.util.Set; +import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -41,7 +42,7 @@ void update( NodeBuilder index, String path, String indexName, NodeBuilder indexMeta, - Set beforeKeys, Set afterKeys); + Set beforeKeys, Set afterKeys) throws CommitFailedException; /** * Check whether an entry for the given key exists. @@ -88,4 +89,6 @@ */ long count(Filter filter, NodeState root, NodeState indexMeta, Set values, int max); + String getIndexNodeName(); + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/UniqueEntryStoreStrategy.java (working copy) @@ -47,6 +47,16 @@ static final Logger LOG = LoggerFactory.getLogger(UniqueEntryStoreStrategy.class); + private final String indexName; + + public UniqueEntryStoreStrategy() { + this(INDEX_CONTENT_NODE_NAME); + } + + public UniqueEntryStoreStrategy(String indexName) { + this.indexName = indexName; + } + @Override public void update( NodeBuilder index, String path, @@ -108,7 +118,7 @@ @Override public Iterable query(final Filter filter, final String indexName, final NodeState indexMeta, final Iterable values) { - final NodeState index = indexMeta.getChildNode(INDEX_CONTENT_NODE_NAME); + final NodeState index = indexMeta.getChildNode(getIndexNodeName()); return new Iterable() { @Override public Iterator iterator() { @@ -157,7 +167,7 @@ @Override public long count(NodeState root, NodeState indexMeta, Set values, int max) { - NodeState index = indexMeta.getChildNode(INDEX_CONTENT_NODE_NAME); + NodeState index = indexMeta.getChildNode(getIndexNodeName()); long count = 0; if (values == null) { PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME); @@ -193,5 +203,9 @@ public long count(final Filter filter, NodeState root, NodeState indexMeta, Set values, int max) { return count(root, indexMeta, values, max); } - + + @Override + public String getIndexNodeName() { + return indexName; + } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditor.java (working copy) @@ -42,9 +42,11 @@ import org.apache.jackrabbit.oak.api.CommitFailedException; import org.apache.jackrabbit.oak.api.PropertyState; import org.apache.jackrabbit.oak.plugins.index.IndexEditor; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.Multiplexers; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.spi.commit.DefaultEditor; import org.apache.jackrabbit.oak.spi.commit.Editor; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -54,8 +56,6 @@ */ class ReferenceEditor extends DefaultEditor implements IndexEditor { - private static final ContentMirrorStoreStrategy STORE = new ContentMirrorStoreStrategy(); - /** Parent editor, or {@code null} if this is the root editor. */ private final ReferenceEditor parent; @@ -101,13 +101,15 @@ */ private final Set newIds; + private final MountInfoProvider mountInfoProvider; + /** * flag marking a reindex, case in which we don't need to keep track of the * newIds set */ private boolean isReindex; - public ReferenceEditor(NodeBuilder definition, NodeState root) { + public ReferenceEditor(NodeBuilder definition, NodeState root,MountInfoProvider mountInfoProvider) { this.parent = null; this.name = null; this.path = "/"; @@ -119,6 +121,7 @@ this.rmWeakRefs = newHashMap(); this.rmIds = newHashSet(); this.newIds = newHashSet(); + this.mountInfoProvider = mountInfoProvider; } private ReferenceEditor(ReferenceEditor parent, String name) { @@ -134,6 +137,7 @@ this.rmIds = parent.rmIds; this.newIds = parent.newIds; this.isReindex = parent.isReindex; + this.mountInfoProvider = parent.mountInfoProvider; } /** @@ -158,6 +162,8 @@ public void leave(NodeState before, NodeState after) throws CommitFailedException { if (parent == null) { + Set refStores = getStrategies(false, REF_NAME); + Set weakRefStores = getStrategies(false, WEAK_REF_NAME); // update references for (Entry> ref : rmRefs.entrySet()) { String uuid = ref.getKey(); @@ -166,7 +172,7 @@ if (newRefs.containsKey(uuid)) { add = newRefs.remove(uuid); } - update(definition, REF_NAME, uuid, add, rm); + update(refStores, definition, REF_NAME, uuid, add, rm); } for (Entry> ref : newRefs.entrySet()) { String uuid = ref.getKey(); @@ -175,10 +181,10 @@ } Set add = ref.getValue(); Set rm = emptySet(); - update(definition, REF_NAME, uuid, add, rm); + update(refStores, definition, REF_NAME, uuid, add, rm); } - checkReferentialIntegrity(root, definition.getNodeState(), + checkReferentialIntegrity(refStores, root, definition.getNodeState(), Sets.difference(rmIds, newIds)); // update weak references @@ -189,17 +195,22 @@ if (newWeakRefs.containsKey(uuid)) { add = newWeakRefs.remove(uuid); } - update(definition, WEAK_REF_NAME, uuid, add, rm); + update(weakRefStores, definition, WEAK_REF_NAME, uuid, add, rm); } for (Entry> ref : newWeakRefs.entrySet()) { String uuid = ref.getKey(); Set add = ref.getValue(); Set rm = emptySet(); - update(definition, WEAK_REF_NAME, uuid, add, rm); + update(weakRefStores, definition, WEAK_REF_NAME, uuid, add, rm); } } } + Set getStrategies(boolean unique, String index) { + return Multiplexers.getStrategies(unique, mountInfoProvider, + definition, index); + } + @Override public void propertyAdded(PropertyState after) { propertyChanged(null, after); @@ -292,35 +303,43 @@ } } - private static void update(NodeBuilder child, String name, String key, - Set add, Set rm) { - NodeBuilder index = child.child(name); - Set empty = of(); - for (String p : rm) { - STORE.update(index, p, name, child, of(key), empty); - } - for (String p : add) { - // TODO do we still need to encode the values? - STORE.update(index, p, name, child, empty, of(key)); + private void update(Set refStores, + NodeBuilder definition, String name, String key, Set add, + Set rm) throws CommitFailedException { + for (IndexStoreStrategy store : refStores) { + Set empty = of(); + for (String p : rm) { + NodeBuilder index = definition.child(store.getIndexNodeName()); + store.update(index, p, name, definition, of(key), empty); + } + for (String p : add) { + // TODO do we still need to encode the values? + NodeBuilder index = definition.child(store.getIndexNodeName()); + store.update(index, p, name, definition, empty, of(key)); + } } } - private static boolean hasReferences(NodeState root, - NodeState definition, - String name, - String key) { + private static boolean hasReferences(IndexStoreStrategy refStore, + NodeState root, + NodeState definition, + String name, + String key) { return definition.hasChildNode(name) - && STORE.count(root, definition, name, of(key), 1) > 0; + && refStore.count(root, definition, of(key), 1) > 0; } - private static void checkReferentialIntegrity(NodeState root, - NodeState definition, - Set idsOfRemovedNodes) + private static void checkReferentialIntegrity(Set refStores, + NodeState root, + NodeState definition, + Set idsOfRemovedNodes) throws CommitFailedException { - for (String id : idsOfRemovedNodes) { - if (hasReferences(root, definition, REF_NAME, id)) { - throw new CommitFailedException(INTEGRITY, 1, - "Unable to delete referenced node"); + for (IndexStoreStrategy store : refStores) { + for (String id : idsOfRemovedNodes) { + if (hasReferences(store, root, definition, REF_NAME, id)) { + throw new CommitFailedException(INTEGRITY, 1, + "Unable to delete referenced node"); + } } } } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceEditorProvider.java (working copy) @@ -16,17 +16,20 @@ */ package org.apache.jackrabbit.oak.plugins.index.reference; -import javax.annotation.Nonnull; - import static org.apache.jackrabbit.oak.plugins.index.reference.NodeReferenceConstants.TYPE; +import javax.annotation.Nonnull; + import org.apache.felix.scr.annotations.Component; import org.apache.felix.scr.annotations.Property; +import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; import org.apache.jackrabbit.oak.plugins.index.IndexConstants; 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.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -35,13 +38,21 @@ @Property(name = IndexConstants.TYPE_PROPERTY_NAME , value = NodeReferenceConstants.TYPE, propertyPrivate = true) public class ReferenceEditorProvider implements IndexEditorProvider { + @Reference + private MountInfoProvider mountInfoProvider = Mounts.defaultMountInfoProvider(); + @Override public Editor getIndexEditor(@Nonnull String type, @Nonnull NodeBuilder definition, @Nonnull NodeState root, @Nonnull IndexUpdateCallback callback) { if (TYPE.equals(type)) { - return new ReferenceEditor(definition, root); + return new ReferenceEditor(definition, root, mountInfoProvider); } return null; } + public ReferenceEditorProvider with(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + return this; + } + } Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndex.java (working copy) @@ -31,9 +31,14 @@ import static org.apache.jackrabbit.oak.spi.query.Cursors.newPathCursor; import java.util.ArrayList; +import java.util.List; +import java.util.Set; -import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; +import org.apache.jackrabbit.oak.plugins.index.property.Multiplexers; +import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexStoreStrategy; import org.apache.jackrabbit.oak.query.index.FilterImpl; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.query.Cursor; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.Filter.PropertyRestriction; @@ -43,6 +48,8 @@ import com.google.common.base.Function; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; /** * Provides a QueryIndex that does lookups for node references based on a custom @@ -51,10 +58,18 @@ */ class ReferenceIndex implements QueryIndex { - private static final ContentMirrorStoreStrategy STORE = new ContentMirrorStoreStrategy(); - private static final double COST = 1; + private final MountInfoProvider mountInfoProvider; + + ReferenceIndex() { + this(Mounts.defaultMountInfoProvider()); + } + + ReferenceIndex(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + } + @Override public double getMinimumCost() { return COST; @@ -110,15 +125,19 @@ return newPathCursor(new ArrayList(), filter.getQueryEngineSettings()); } - private static Cursor lookup(NodeState root, String uuid, + private Cursor lookup(NodeState root, String uuid, final String name, String index, Filter filter) { NodeState indexRoot = root.getChildNode(INDEX_DEFINITIONS_NAME) .getChildNode(NAME); if (!indexRoot.exists()) { return newPathCursor(new ArrayList(), filter.getQueryEngineSettings()); } - Iterable paths = STORE.query(new FilterImpl(), index + "(" - + uuid + ")", indexRoot, index, ImmutableSet.of(uuid)); + List> iterables = Lists.newArrayList(); + for (IndexStoreStrategy s : getStrategies(indexRoot, mountInfoProvider, index)) { + iterables.add(s.query(new FilterImpl(), index + "(" + + uuid + ")", indexRoot, ImmutableSet.of(uuid))); + } + Iterable paths = Iterables.concat(iterables); if (!"*".equals(name)) { paths = filter(paths, new Predicate() { @@ -137,6 +156,12 @@ return newPathCursor(paths, filter.getQueryEngineSettings()); } + private static Set getStrategies(NodeState definition, + MountInfoProvider mountInfoProvider, String index) { + return Multiplexers.getStrategies(false, mountInfoProvider, definition, + index); + } + @Override public String getPlan(Filter filter, NodeState root) { StringBuilder buff = new StringBuilder("reference"); Index: oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexProvider.java =================================================================== --- oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexProvider.java (revision 1753764) +++ oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexProvider.java (working copy) @@ -21,7 +21,10 @@ import javax.annotation.Nonnull; import org.apache.felix.scr.annotations.Component; +import org.apache.felix.scr.annotations.Reference; import org.apache.felix.scr.annotations.Service; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; import org.apache.jackrabbit.oak.spi.query.QueryIndex; import org.apache.jackrabbit.oak.spi.query.QueryIndexProvider; import org.apache.jackrabbit.oak.spi.state.NodeState; @@ -35,9 +38,17 @@ @Service(QueryIndexProvider.class) public class ReferenceIndexProvider implements QueryIndexProvider { + @Reference + private MountInfoProvider mountInfoProvider = Mounts.defaultMountInfoProvider(); + @Override @Nonnull public List getQueryIndexes(NodeState state) { - return ImmutableList. of(new ReferenceIndex()); + return ImmutableList. of(new ReferenceIndex(mountInfoProvider)); + } + + public ReferenceIndexProvider with(MountInfoProvider mountInfoProvider) { + this.mountInfoProvider = mountInfoProvider; + return this; } } Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiplexersTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiplexersTest.java (revision 0) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/MultiplexersTest.java (revision 0) @@ -0,0 +1,62 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.jackrabbit.oak.plugins.index.property; + +import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME; +import static org.apache.jackrabbit.oak.plugins.index.property.Multiplexers.getIndexNodeName; +import static org.apache.jackrabbit.oak.plugins.index.property.Multiplexers.getNodeForMount; +import static org.junit.Assert.assertEquals; + +import org.apache.jackrabbit.oak.plugins.multiplex.SimpleMountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mount; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.mount.Mounts; +import org.junit.Test; + +public class MultiplexersTest { + + @Test + public void defaultSetup() throws Exception { + assertEquals( + INDEX_CONTENT_NODE_NAME, + getIndexNodeName(Mounts.defaultMountInfoProvider(), "/foo", + INDEX_CONTENT_NODE_NAME)); + assertEquals(INDEX_CONTENT_NODE_NAME, + getNodeForMount(Mounts.defaultMount(), INDEX_CONTENT_NODE_NAME)); + } + + @Test + public void customNodeName() throws Exception { + MountInfoProvider mip = SimpleMountInfoProvider.newBuilder() + .mount("foo", "/a", "/b").build(); + + Mount m = mip.getMountByName("foo"); + + assertEquals(":index", + getIndexNodeName(mip, "/foo", INDEX_CONTENT_NODE_NAME)); + assertEquals(":index", + getNodeForMount(mip.getDefaultMount(), INDEX_CONTENT_NODE_NAME)); + + assertEquals(":" + m.getPathFragmentName() + "-index", + getIndexNodeName(mip, "/a", INDEX_CONTENT_NODE_NAME)); + assertEquals(":" + m.getPathFragmentName() + "-index", + getNodeForMount(m, INDEX_CONTENT_NODE_NAME)); + } +} \ No newline at end of file Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java (revision 1753764) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/PropertyIndexTest.java (working copy) @@ -21,6 +21,7 @@ import static org.apache.jackrabbit.JcrConstants.NT_BASE; import static org.apache.jackrabbit.JcrConstants.NT_FILE; import static org.apache.jackrabbit.JcrConstants.NT_UNSTRUCTURED; +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.INDEX_PATH; import static org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition; @@ -30,6 +31,7 @@ import static org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState.EMPTY_NODE; import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; import static org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.spi.state.NodeStateUtils.getNode; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; @@ -43,6 +45,7 @@ import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; import org.apache.jackrabbit.oak.plugins.index.property.strategy.ContentMirrorStoreStrategy; import org.apache.jackrabbit.oak.plugins.memory.EmptyNodeState; +import org.apache.jackrabbit.oak.plugins.multiplex.SimpleMountInfoProvider; import org.apache.jackrabbit.oak.query.NodeStateNodeTypeInfoProvider; import org.apache.jackrabbit.oak.query.QueryEngineSettings; import org.apache.jackrabbit.oak.query.ast.NodeTypeInfo; @@ -53,6 +56,8 @@ import org.apache.jackrabbit.oak.query.index.TraversingIndex; import org.apache.jackrabbit.oak.spi.commit.CommitInfo; import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.mount.Mount; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; import org.apache.jackrabbit.oak.spi.query.Filter; import org.apache.jackrabbit.oak.spi.query.PropertyValues; import org.apache.jackrabbit.oak.spi.state.NodeBuilder; @@ -746,6 +751,107 @@ assertEquals("/oak:index/foo", idxDefn.getString(INDEX_PATH)); } + @Test + public void singleMount() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, false, ImmutableSet.of("foo"), null); + index.setProperty("entryCount", -1); + NodeState before = builder.getNodeState(); + + // Add some content and process it through the property index hook + builder.child("a").setProperty("foo", "abc"); + builder.child("b").child("x").setProperty("foo", "abc"); + builder.child("a").child("x").setProperty("foo", "abc"); + builder.child("m").child("n").setProperty("foo", "abc"); + builder.child("m").child("n").child("o").setProperty("foo", "abc"); + builder.child("m").setProperty("foo", "abc"); + + NodeState after = builder.getNodeState(); + + MountInfoProvider mip = SimpleMountInfoProvider.newBuilder() + .mount("foo", "/a", "/m/n") + .build(); + + Mount fooMount = mip.getMountByName("foo"); + Mount defMount = mip.getDefaultMount(); + + EditorHook hook = new EditorHook( + new IndexUpdateProvider(new PropertyIndexEditorProvider().with(mip))); + + NodeState indexed = hook.processCommit(before, after, CommitInfo.EMPTY); + + FilterImpl f = createFilter(indexed, NT_BASE); + + // Query the index + PropertyIndexLookup lookup = new PropertyIndexLookup(indexed,mip); + assertEquals(ImmutableSet.of("a", "b/x", "a/x", "m", "m/n", "m/n/o"), find(lookup, "foo", "abc", f)); + assertEquals(ImmutableSet.of(), find(lookup, "foo", "ghi", f)); + + assertTrue(getNode(indexed, "/oak:index/foo/:index").exists()); + + //Separate node for mount + assertTrue(getNode(indexed, "/oak:index/foo/"+ getNodeForMount(fooMount)).exists()); + + //Index entries for paths in foo mount should go to :oak:foo-index + assertTrue(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/a", "abc")).exists()); + assertTrue(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/a/x", "abc")).exists()); + assertTrue(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/m/n", "abc")).exists()); + assertTrue(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/m/n/o", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/a", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/a/x", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/m/n", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/m/n/o", "abc")).exists()); + + //All other index entries should go to :index + assertTrue(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/b", "abc")).exists()); + assertTrue(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/b/x", "abc")).exists()); + assertTrue(getNode(indexed, pathInIndex(defMount, "/oak:index/foo", "/m", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/b", "abc")).exists()); + assertFalse(getNode(indexed, pathInIndex(fooMount, "/oak:index/foo", "/b/x", "abc")).exists()); + + //System.out.println(NodeStateUtils.toString(getNode(indexed, "/oak:index/foo"))); + + } + + @Test(expected = CommitFailedException.class) + public void mountAndUniqueIndexes() throws Exception { + NodeState root = INITIAL_CONTENT; + + // Add index definition + NodeBuilder builder = root.builder(); + NodeBuilder index = createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo", + true, true, ImmutableSet.of("foo"), null); + index.setProperty("entryCount", -1); + NodeState before = builder.getNodeState(); + + MountInfoProvider mip = SimpleMountInfoProvider.newBuilder() + .mount("foo", "/a") + .build(); + + builder.child("a").setProperty("foo", "abc"); + builder.child("b").setProperty("foo", Arrays.asList("abc", "def"), + Type.STRINGS); + NodeState after = builder.getNodeState(); + + EditorHook hook = new EditorHook( + new IndexUpdateProvider(new PropertyIndexEditorProvider().with(mip))); + // should throw + hook.processCommit(before, after, CommitInfo.EMPTY); + } + + private static String pathInIndex(Mount mount, + String indexPath, String indexedPath, String indexedValue){ + return indexPath + "/" + getNodeForMount(mount) + "/" + indexedValue + indexedPath; + } + + private static String getNodeForMount(Mount mount) { + return Multiplexers.getNodeForMount(mount, INDEX_CONTENT_NODE_NAME); + } + private int getResultSize(NodeState indexed, String name, String value){ FilterImpl f = createFilter(indexed, NT_BASE); Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategyTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategyTest.java (revision 1753764) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/property/strategy/ContentMirrorStoreStrategyTest.java (working copy) @@ -63,7 +63,7 @@ * */ @Test - public void testIndexPruning() { + public void testIndexPruning() throws CommitFailedException { IndexStoreStrategy store = new ContentMirrorStoreStrategy(); NodeState root = EMPTY_NODE; Index: oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexTest.java =================================================================== --- oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexTest.java (revision 0) +++ oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/index/reference/ReferenceIndexTest.java (revision 0) @@ -0,0 +1,150 @@ +/* + * 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.reference; + +import java.util.Collections; +import java.util.List; + +import javax.jcr.PropertyType; + +import org.apache.jackrabbit.oak.api.Type; +import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider; +import org.apache.jackrabbit.oak.plugins.multiplex.SimpleMountInfoProvider; +import org.apache.jackrabbit.oak.query.NodeStateNodeTypeInfoProvider; +import org.apache.jackrabbit.oak.query.QueryEngineSettings; +import org.apache.jackrabbit.oak.query.ast.NodeTypeInfo; +import org.apache.jackrabbit.oak.query.ast.NodeTypeInfoProvider; +import org.apache.jackrabbit.oak.query.ast.Operator; +import org.apache.jackrabbit.oak.query.ast.SelectorImpl; +import org.apache.jackrabbit.oak.query.index.FilterImpl; +import org.apache.jackrabbit.oak.spi.commit.CommitInfo; +import org.apache.jackrabbit.oak.spi.commit.EditorHook; +import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider; +import org.apache.jackrabbit.oak.spi.query.Cursor; +import org.apache.jackrabbit.oak.spi.query.Filter; +import org.apache.jackrabbit.oak.spi.query.QueryIndex; +import org.apache.jackrabbit.oak.spi.state.NodeBuilder; +import org.apache.jackrabbit.oak.spi.state.NodeState; +import org.apache.jackrabbit.oak.spi.state.NodeStateUtils; +import org.junit.Test; + +import static com.google.common.collect.ImmutableList.of; +import static com.google.common.collect.Lists.newArrayList; +import static org.apache.jackrabbit.JcrConstants.NT_BASE; +import static org.apache.jackrabbit.oak.plugins.memory.PropertyStates.createProperty; +import static org.apache.jackrabbit.oak.plugins.nodetype.write.InitialContent.INITIAL_CONTENT; +import static org.apache.jackrabbit.oak.spi.query.PropertyValues.newReference; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.assertEquals; + +public class ReferenceIndexTest { + + @Test + public void basicReferenceHandling() throws Exception{ + NodeState root = INITIAL_CONTENT; + + NodeBuilder builder = root.builder(); + NodeState before = builder.getNodeState(); + + builder.child("a").setProperty(createProperty("foo", "u1", Type.REFERENCE)); + builder.child("b").setProperty(createProperty("foo", "u1", Type.REFERENCE)); + builder.child("c").setProperty(createProperty("foo", "u2", Type.WEAKREFERENCE)); + + NodeState after = builder.getNodeState(); + EditorHook hook = new EditorHook( + new IndexUpdateProvider(new ReferenceEditorProvider())); + + NodeState indexed = hook.processCommit(before, after, CommitInfo.EMPTY); + FilterImpl f = createFilter(indexed, NT_BASE); + f.restrictProperty("*", Operator.EQUAL, newReference("u1"), PropertyType.REFERENCE); + + assertFilter(f, new ReferenceIndex(), indexed, of("/a", "/b")); + + FilterImpl f2 = createFilter(indexed, NT_BASE); + f2.restrictProperty("*", Operator.EQUAL, newReference("u2"), PropertyType.WEAKREFERENCE); + assertFilter(f2, new ReferenceIndex(), indexed, of("/c")); + } + + @Test + public void referenceHandlingWithMounts() throws Exception{ + NodeState root = INITIAL_CONTENT; + + NodeBuilder builder = root.builder(); + NodeState before = builder.getNodeState(); + + builder.child("a").child("x").setProperty(createProperty("foo", "u1", Type.REFERENCE)); + builder.child("b").setProperty(createProperty("foo", "u1", Type.REFERENCE)); + builder.child("c").setProperty(createProperty("foo", "u1", Type.WEAKREFERENCE)); + + builder.child("d").setProperty(createProperty("foo", "u2", Type.WEAKREFERENCE)); + builder.child("a").child("y").setProperty(createProperty("foo", "u1", Type.WEAKREFERENCE)); + + NodeState after = builder.getNodeState(); + + MountInfoProvider mip = SimpleMountInfoProvider.newBuilder() + .mount("foo", "/a") + .build(); + + EditorHook hook = new EditorHook( + new IndexUpdateProvider(new ReferenceEditorProvider().with(mip))); + + ReferenceIndex referenceIndex = new ReferenceIndex(mip); + + NodeState indexed = hook.processCommit(before, after, CommitInfo.EMPTY); + FilterImpl f = createFilter(indexed, NT_BASE); + f.restrictProperty("*", Operator.EQUAL, newReference("u1"), PropertyType.REFERENCE); + + // System.out.println(NodeStateUtils.toString(NodeStateUtils.getNode(indexed, "/oak:index/reference"))); + assertFilter(f, referenceIndex, indexed, of("/a/x", "/b")); + + FilterImpl f2 = createFilter(indexed, NT_BASE); + f2.restrictProperty("*", Operator.EQUAL, newReference("u1"), PropertyType.WEAKREFERENCE); + assertFilter(f2, referenceIndex, indexed, of("/c", "/a/y")); + } + + + //TODO Integrity check - Add node with id=1 and add a reference to that and then remove the node + //Removal + + @SuppressWarnings("Duplicates") + private static FilterImpl createFilter(NodeState root, String nodeTypeName) { + NodeTypeInfoProvider nodeTypes = new NodeStateNodeTypeInfoProvider(root); + NodeTypeInfo type = nodeTypes.getNodeTypeInfo(nodeTypeName); + SelectorImpl selector = new SelectorImpl(type, nodeTypeName); + return new FilterImpl(selector, "SELECT * FROM [" + nodeTypeName + "]", new QueryEngineSettings()); + } + + private static List assertFilter(Filter filter, QueryIndex queryIndex, + NodeState indexed, List expected) { + Cursor cursor = queryIndex.query(filter, indexed); + List paths = newArrayList(); + while (cursor.hasNext()) { + paths.add(cursor.next().getPath()); + } + Collections.sort(paths); + for (String p : expected) { + assertTrue("Expected path " + p + " not found", paths.contains(p)); + } + assertEquals("Result set size is different \nExpected: " + + expected + "\nActual: " + paths, expected.size(), paths.size()); + return paths; + } + +} \ No newline at end of file