Author: rombert
Date: Tue Sep 5 15:01:28 2017
New Revision: 1807361
URL: http://svn.apache.org/viewvc?rev=1807361&view=rev
Log:
OAK-6506 - Ensure unique property indexes are consistent when mounting
NodeStores
Add a custom UniqueIndexNodeStoreChecker which validates consistency of
unique indexes from mounts.
Added:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreChecker.java
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreCheckerTest.java
Modified:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreBuilderTest.java
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/ErrorHolder.java
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/MountedNodeStoreChecker.java
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksService.java
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreChecker.java
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksServiceTest.java
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreCheckerTest.java
Modified:
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreBuilderTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreBuilderTest.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreBuilderTest.java
(original)
+++
jackrabbit/oak/trunk/oak-it/src/test/java/org/apache/jackrabbit/oak/composite/CompositeNodeStoreBuilderTest.java
Tue Sep 5 15:01:28 2017
@@ -115,7 +115,7 @@ public class CompositeNodeStoreBuilderTe
new CompositeNodeStore.Builder(mip, root)
.addMount("readOnly", mount)
- .with(new NodeStoreChecksService(Collections.singletonList(new
NodeTypeMountedNodeStoreChecker(JcrConstants.MIX_VERSIONABLE, "test error"))))
+ .with(new NodeStoreChecksService(mip,
Collections.singletonList(new
NodeTypeMountedNodeStoreChecker(JcrConstants.MIX_VERSIONABLE, "test error"))))
.build();
}
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/ErrorHolder.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/ErrorHolder.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/ErrorHolder.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/ErrorHolder.java
Tue Sep 5 15:01:28 2017
@@ -34,6 +34,15 @@ class ErrorHolder {
}
}
+ public void report(MountedNodeStore firstNS, String firstPath,
MountedNodeStore secondNS, String secondPath, String value, String error) {
+ errors.add(String.format("For NodeStore mount %s, path %s, and
NodeStore mount %s, path %s, encountered the following clash for value %s:
'%s'",
+ firstNS.getMount().getName(), firstPath,
secondNS.getMount().getName(), secondPath, value, error));
+ if ( errors.size() == FAIL_IMMEDIATELY_THRESHOLD ) {
+ end();
+ }
+
+ }
+
public void end() {
if ( errors.isEmpty() ) {
return;
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/MountedNodeStoreChecker.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/MountedNodeStoreChecker.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/MountedNodeStoreChecker.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/MountedNodeStoreChecker.java
Tue Sep 5 15:01:28 2017
@@ -21,6 +21,7 @@ package org.apache.jackrabbit.oak.compos
import org.apache.jackrabbit.oak.api.Tree;
import org.apache.jackrabbit.oak.composite.MountedNodeStore;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
/**
@@ -33,8 +34,8 @@ import org.apache.jackrabbit.oak.spi.sta
*/
public interface MountedNodeStoreChecker<T> {
- public T createContext(NodeStore globalStore);
+ public T createContext(NodeStore globalStore, MountInfoProvider mip);
- void check(MountedNodeStore mountedStore, Tree tree, ErrorHolder
errorHolder, T context);
+ boolean check(MountedNodeStore mountedStore, Tree tree, ErrorHolder
errorHolder, T context);
}
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksService.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksService.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksService.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksService.java
Tue Sep 5 15:01:28 2017
@@ -27,6 +27,7 @@ import org.apache.jackrabbit.oak.api.Tre
import org.apache.jackrabbit.oak.composite.MountedNodeStore;
import org.apache.jackrabbit.oak.plugins.tree.TreeFactory;
import org.apache.jackrabbit.oak.spi.mount.Mount;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -42,6 +43,9 @@ public class NodeStoreChecksService impl
unbind = "unbindChecker",
referenceInterface = MountedNodeStoreChecker.class)
private List<MountedNodeStoreChecker<?>> checkers = new
CopyOnWriteArrayList<>();
+
+ @Reference
+ private MountInfoProvider mip;
// used by SCR
public NodeStoreChecksService() {
@@ -49,8 +53,9 @@ public class NodeStoreChecksService impl
}
// visible for testing
- public NodeStoreChecksService(List<MountedNodeStoreChecker<?>> checkers) {
+ public NodeStoreChecksService(MountInfoProvider mip,
List<MountedNodeStoreChecker<?>> checkers) {
this.checkers = checkers;
+ this.mip = mip;
}
@Override
@@ -72,7 +77,7 @@ public class NodeStoreChecksService impl
private <T> void check(MountedNodeStore mountedStore, ErrorHolder
errorHolder, NodeStore globalStore,
MountedNodeStoreChecker<T> c) {
- T context = c.createContext(globalStore);
+ T context = c.createContext(globalStore, mip);
Tree mountRoot =
TreeFactory.createReadOnlyTree(mountedStore.getNodeStore().getRoot());
visit(mountRoot, mountedStore, errorHolder, context, c);
@@ -89,11 +94,12 @@ public class NodeStoreChecksService impl
boolean mounted = mount.isMounted(tree.getPath());
+ boolean keepGoing = true;
if ( mounted ) {
- c.check(mountedStore, tree, errorHolder, context);
+ keepGoing = c.check(mountedStore, tree, errorHolder, context);
}
- if ( mounted || under ) {
+ if ( ( mounted || under ) && keepGoing ) {
tree.getChildren().forEach( child -> visit(child, mountedStore,
errorHolder, context, c));
}
}
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreChecker.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreChecker.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreChecker.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreChecker.java
Tue Sep 5 15:01:28 2017
@@ -31,6 +31,7 @@ import org.apache.jackrabbit.oak.composi
import org.apache.jackrabbit.oak.namepath.NamePathMapper;
import org.apache.jackrabbit.oak.plugins.nodetype.ReadOnlyNodeTypeManager;
import org.apache.jackrabbit.oak.plugins.tree.RootFactory;
+import org.apache.jackrabbit.oak.spi.mount.MountInfoProvider;
import org.apache.jackrabbit.oak.spi.state.NodeStore;
import org.osgi.service.component.ComponentContext;
import org.slf4j.Logger;
@@ -78,7 +79,7 @@ public class NodeTypeMountedNodeStoreChe
}
@Override
- public Context createContext(NodeStore globalStore) {
+ public Context createContext(NodeStore globalStore, MountInfoProvider mip)
{
Root globalRoot =
RootFactory.createReadOnlyRoot(globalStore.getRoot());
ReadOnlyNodeTypeManager typeManager =
ReadOnlyNodeTypeManager.getInstance(globalRoot, NamePathMapper.DEFAULT);
@@ -87,12 +88,14 @@ public class NodeTypeMountedNodeStoreChe
}
@Override
- public void check(MountedNodeStore mountedStore, Tree tree, ErrorHolder
errorHolder, Context context) {
+ public boolean check(MountedNodeStore mountedStore, Tree tree, ErrorHolder
errorHolder, Context context) {
if ( context.getTypeManager().isNodeType(tree, invalidNodeType) &&
!isExcluded(mountedStore, tree, context) ) {
errorHolder.report(mountedStore, tree.getPath(), errorLabel);
}
+
+ return true;
}
private boolean isExcluded(MountedNodeStore mountedStore, Tree tree,
Context context) {
Added:
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreChecker.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreChecker.java?rev=1807361&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreChecker.java
(added)
+++
jackrabbit/oak/trunk/oak-store-composite/src/main/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreChecker.java
Tue Sep 5 15:01:28 2017
@@ -0,0 +1,293 @@
+/*
+ * 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.composite.checks;
+
+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.UNIQUE_PROPERTY_NAME;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Service;
+import org.apache.jackrabbit.oak.api.Tree;
+import org.apache.jackrabbit.oak.composite.MountedNodeStore;
+import org.apache.jackrabbit.oak.plugins.index.property.Multiplexers;
+import org.apache.jackrabbit.oak.plugins.index.property.strategy.IndexEntry;
+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.query.Filter;
+import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
+import org.apache.jackrabbit.oak.spi.state.NodeState;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.collect.Maps;
+
+/**
+ * Checker that ensures the consistency of unique entries in the various mounts
+ *
+ * <p>For all unique indexes, it checks that the uniqueness constraint holds
when
+ * taking into account the combined index from all the mounts, including the
global one.</p>
+ *
+ * <p>Being a one-off check, it does not strictly implement the {@link
#check(MountedNodeStore, Tree, ErrorHolder, Context)}
+ * contract in terms of navigating the specified tree, but instead accesses
the index definitions node directly
+ * on first access and skips all subsequent executions.</p>
+ *
+ */
+@Component
+@Service(MountedNodeStoreChecker.class)
+public class UniqueIndexNodeStoreChecker implements
MountedNodeStoreChecker<UniqueIndexNodeStoreChecker.Context> {
+
+ private static final Logger LOG =
LoggerFactory.getLogger(UniqueIndexNodeStoreChecker.class);
+
+ @Override
+ public Context createContext(NodeStore globalStore, MountInfoProvider mip)
{
+
+ Context ctx = new Context(mip);
+
+ // read definitions from oak:index, and pick all unique indexes
+ NodeState indexDefs =
globalStore.getRoot().getChildNode(INDEX_DEFINITIONS_NAME);
+ for ( ChildNodeEntry indexDef : indexDefs.getChildNodeEntries() ) {
+ if ( indexDef.getNodeState().hasProperty(UNIQUE_PROPERTY_NAME) &&
+ indexDef.getNodeState().getBoolean(UNIQUE_PROPERTY_NAME) )
{
+ ctx.add(indexDef, mip.getDefaultMount(), indexDefs);
+ ctx.track(new MountedNodeStore(mip.getDefaultMount() ,
globalStore));
+ }
+ }
+
+ return ctx;
+ }
+
+ @Override
+ public boolean check(MountedNodeStore mountedStore, Tree tree, ErrorHolder
errorHolder, Context context) {
+ context.track(mountedStore);
+
+ // gather index definitions owned by this mount
+ NodeState indexDefs =
mountedStore.getNodeStore().getRoot().getChildNode(INDEX_DEFINITIONS_NAME);
+
+ for ( ChildNodeEntry indexDef : indexDefs.getChildNodeEntries() ) {
+ if ( indexDef.getNodeState().hasProperty(UNIQUE_PROPERTY_NAME) &&
+ indexDef.getNodeState().getBoolean(UNIQUE_PROPERTY_NAME) )
{
+
+ String mountIndexDefName =
Multiplexers.getNodeForMount(mountedStore.getMount(), INDEX_CONTENT_NODE_NAME);
+
+ NodeState mountIndexDef =
indexDef.getNodeState().getChildNode(mountIndexDefName);
+
+ if ( mountIndexDef.exists() ) {
+ context.add(indexDef, mountedStore.getMount(), indexDefs);
+ }
+ }
+ }
+
+ // execute checks
+ context.runChecks(context, errorHolder);
+
+ return false;
+ }
+
+ static class Context {
+ private final MountInfoProvider mip;
+ private final Map<String, IndexCombination> combinations = new
HashMap<>();
+ private final Map<String, MountedNodeStore> mountedNodeStoresByName =
Maps.newHashMap();
+
+ Context(MountInfoProvider mip) {
+ this.mip = mip;
+ }
+
+ public void track(MountedNodeStore mountedNodeStore) {
+ mountedNodeStoresByName.put(mountedNodeStore.getMount().getName(),
mountedNodeStore);
+ }
+
+ public void add(ChildNodeEntry rootIndexDef, Mount mount, NodeState
indexDef) {
+
+ IndexCombination combination =
combinations.get(rootIndexDef.getName());
+ if ( combination == null ) {
+ combination = new IndexCombination(rootIndexDef);
+ combinations.put(rootIndexDef.getName(), combination);
+ }
+
+ combination.addEntry(mount, indexDef);
+ }
+
+ public MountInfoProvider getMountInfoProvider() {
+
+ return mip;
+ }
+
+ public void runChecks(Context context, ErrorHolder errorHolder) {
+ for ( IndexCombination combination: combinations.values() ) {
+ combination.runCheck(context, errorHolder);
+ }
+ }
+ }
+
+ static class IndexCombination {
+ private final ChildNodeEntry rootIndexDef;
+ private final Map<Mount, NodeState> indexEntries = Maps.newHashMap();
+ private final List<Mount[]> checked = new ArrayList<>();
+ // bounded as the ErrorHolder has a reasonable limit of entries before
it fails immediately
+ private final Set<String> reportedConflictingValues = new HashSet<>();
+
+ IndexCombination(ChildNodeEntry rootIndexDef) {
+ this.rootIndexDef = rootIndexDef;
+ }
+
+ public void addEntry(Mount mount, NodeState indexDef) {
+
+ if ( !indexEntries.containsKey(mount) )
+ indexEntries.put(mount, indexDef);
+ }
+
+ public void runCheck(Context context, ErrorHolder errorHolder) {
+
+ for ( Map.Entry<Mount, NodeState> indexEntry :
indexEntries.entrySet() ) {
+ for ( Map.Entry<Mount, NodeState> indexEntry2 :
indexEntries.entrySet() ) {
+
+ // same entry, skip
+ if ( indexEntry.getKey().equals(indexEntry2.getKey()) ) {
+ continue;
+ }
+
+ // ensure we don't check twice
+ if ( wasChecked(indexEntry.getKey(), indexEntry2.getKey()
)) {
+ continue;
+ }
+
+ check(indexEntry, indexEntry2, context, errorHolder);
+
+ recordChecked(indexEntry.getKey(), indexEntry2.getKey());
+ }
+ }
+ }
+
+ private boolean wasChecked(Mount first, Mount second) {
+
+ for ( Mount[] checkedEntry : checked ) {
+ if ( ( checkedEntry[0].equals(first) &&
checkedEntry[1].equals(second) ) ||
+ ( checkedEntry[1].equals(first) &&
checkedEntry[0].equals(second))) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private void recordChecked(Mount first, Mount second) {
+
+ checked.add(new Mount[] { first, second });
+ }
+
+ private void check(Entry<Mount, NodeState> indexEntry, Entry<Mount,
NodeState> indexEntry2, Context ctx, ErrorHolder errorHolder) {
+
+ String indexName = rootIndexDef.getName();
+
+ // optimisation: sort strategies to ensuer that we query all the
entries from the mount and check them
+ // against the default. this way we'll run a significantly smaller
number of checks. The assumption is
+ // that the non-default mount will always hold a larger number of
entries
+ TreeSet<StrategyWrapper> wrappers = new TreeSet<>();
+ wrappers.add(getWrapper(indexEntry, indexName, ctx));
+ wrappers.add(getWrapper(indexEntry2, indexName, ctx));
+
+ StrategyWrapper wrapper = wrappers.first();
+ StrategyWrapper wrapper2 = wrappers.last();
+
+ LOG.info("Checking index definitions for {} between mounts {} and
{}", indexName, wrapper.mount.getName(), wrapper2.mount.getName());
+
+ for ( IndexEntry hit : wrapper.queryAll() ) {
+ Optional<IndexEntry> result =
wrapper2.queryOne(hit.getPropertyValue());
+ if ( result.isPresent() ) {
+ IndexEntry hit2 = result.get();
+ if (
reportedConflictingValues.add(hit.getPropertyValue())) {
+ errorHolder.report(wrapper.nodeStore, hit.getPath(),
wrapper2.nodeStore, hit2.getPath(),
+ hit.getPropertyValue(), "duplicate unique
index entry");
+ }
+ }
+ }
+ }
+
+ private StrategyWrapper getWrapper(Entry<Mount, NodeState> indexEntry,
String indexName, Context ctx) {
+
+ NodeState indexNode = indexEntry.getValue();
+ Mount mount = indexEntry.getKey();
+ MountedNodeStore mountedNodeStore =
ctx.mountedNodeStoresByName.get(mount.getName());
+
+ UniqueEntryStoreStrategy strategy = mount.isDefault() ? new
UniqueEntryStoreStrategy() :
+ new
UniqueEntryStoreStrategy(Multiplexers.getNodeForMount(mount,
INDEX_CONTENT_NODE_NAME));
+
+ return new StrategyWrapper(strategy,
indexNode.getChildNode(indexName), indexName, mountedNodeStore);
+ }
+ }
+
+ private static class StrategyWrapper implements
Comparable<StrategyWrapper> {
+
+ private static final int PRIORITY_DEFAULT = 0;
+ private static final int PRIORITY_MOUNT = 100;
+
+ private final UniqueEntryStoreStrategy strategy;
+ private final int priority;
+ private final NodeState indexNode;
+ private final Mount mount;
+ private final MountedNodeStore nodeStore;
+ private final String indexName;
+
+ private StrategyWrapper(UniqueEntryStoreStrategy strategy, NodeState
indexNode, String indexName, MountedNodeStore nodeStore) {
+ this.strategy = strategy;
+ this.mount = nodeStore.getMount();
+ this.indexNode = indexNode;
+ this.nodeStore = nodeStore;
+ this.indexName = indexName;
+ this.priority = mount.isDefault() ? PRIORITY_MOUNT :
PRIORITY_DEFAULT;
+ }
+
+ @Override
+ public int compareTo(StrategyWrapper o) {
+ int prioCmp = Integer.compare(priority, o.priority);
+ if ( prioCmp != 0 ) {
+ return prioCmp;
+ }
+ return mount.getName().compareTo(o.mount.getName());
+ }
+
+ public Iterable<IndexEntry> queryAll() {
+ return strategy.queryEntries(Filter.EMPTY_FILTER, indexName,
indexNode, null);
+ }
+
+ public Optional<IndexEntry> queryOne(String value) {
+
+ Iterable<IndexEntry> results =
strategy.queryEntries(Filter.EMPTY_FILTER, indexName, indexNode,
Collections.singleton(value));
+ if ( !results.iterator().hasNext() ) {
+ return Optional.empty();
+ }
+
+ return Optional.of(results.iterator().next());
+ }
+ }
+}
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksServiceTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksServiceTest.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksServiceTest.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeStoreChecksServiceTest.java
Tue Sep 5 15:01:28 2017
@@ -17,6 +17,7 @@
package org.apache.jackrabbit.oak.composite.checks;
import static com.google.common.base.Preconditions.checkNotNull;
+import static
org.apache.jackrabbit.oak.spi.mount.Mounts.defaultMountInfoProvider;
import java.util.Arrays;
@@ -70,7 +71,7 @@ public class NodeStoreChecksServiceTest
@Test(expected = IllegalRepositoryStateException.class)
public void failOnNodeCoveredByMount() {
- NodeStoreChecksService checks = new
NodeStoreChecksService(Arrays.asList(new FailOnTreeNameChecker("third")));
+ NodeStoreChecksService checks = new
NodeStoreChecksService(defaultMountInfoProvider(), Arrays.asList(new
FailOnTreeNameChecker("third")));
checks.check(globalStore, new MountedNodeStore(mount, mountedStore));
}
@@ -78,7 +79,7 @@ public class NodeStoreChecksServiceTest
@Test
public void doNotFailOnNodeNotCoveredByMount() {
- NodeStoreChecksService checks = new
NodeStoreChecksService(Arrays.asList(new FailOnTreeNameChecker("not-covered")));
+ NodeStoreChecksService checks = new
NodeStoreChecksService(defaultMountInfoProvider(), Arrays.asList(new
FailOnTreeNameChecker("not-covered")));
checks.check(globalStore, new MountedNodeStore(mount, mountedStore));
}
@@ -92,14 +93,16 @@ public class NodeStoreChecksServiceTest
}
@Override
- public Void createContext(NodeStore globalStore) {
+ public Void createContext(NodeStore globalStore, MountInfoProvider
mip) {
return null;
}
@Override
- public void check(MountedNodeStore mountedStore, Tree tree,
ErrorHolder errorHolder, Void context) {
+ public boolean check(MountedNodeStore mountedStore, Tree tree,
ErrorHolder errorHolder, Void context) {
if ( name.equals(tree.getName()))
errorHolder.report(mountedStore, tree.getPath(), "test
failure");
+
+ return true;
}
}
Modified:
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreCheckerTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreCheckerTest.java?rev=1807361&r1=1807360&r2=1807361&view=diff
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreCheckerTest.java
(original)
+++
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/NodeTypeMountedNodeStoreCheckerTest.java
Tue Sep 5 15:01:28 2017
@@ -58,7 +58,7 @@ public class NodeTypeMountedNodeStoreChe
.build();
NodeTypeMountedNodeStoreChecker checker = new
NodeTypeMountedNodeStoreChecker(JcrConstants.MIX_REFERENCEABLE, "test error");
- Context context = checker.createContext(root);
+ Context context = checker.createContext(root, mip);
ErrorHolder errorHolder = new ErrorHolder();
checker.check(new MountedNodeStore(mip.getMountByName("first"),
mount), TreeFactory.createReadOnlyTree(mount.getRoot()).getChild("first"),
errorHolder, context);
@@ -88,7 +88,7 @@ public class NodeTypeMountedNodeStoreChe
NodeTypeMountedNodeStoreChecker checker = new
NodeTypeMountedNodeStoreChecker(JcrConstants.MIX_REFERENCEABLE, "test error",
JcrConstants.NT_RESOURCE);
- Context context = checker.createContext(root);
+ Context context = checker.createContext(root, mip);
ErrorHolder errorHolder = new ErrorHolder();
checker.check(new MountedNodeStore(mip.getMountByName("first"),
mount), TreeFactory.createReadOnlyTree(mount.getRoot()).getChild("first"),
errorHolder, context);
Added:
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreCheckerTest.java
URL:
http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreCheckerTest.java?rev=1807361&view=auto
==============================================================================
---
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreCheckerTest.java
(added)
+++
jackrabbit/oak/trunk/oak-store-composite/src/test/java/org/apache/jackrabbit/oak/composite/checks/UniqueIndexNodeStoreCheckerTest.java
Tue Sep 5 15:01:28 2017
@@ -0,0 +1,177 @@
+/*
+ * 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.composite.checks;
+
+import static
org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_DEFINITIONS_NAME;
+import static
org.apache.jackrabbit.oak.plugins.index.IndexUtils.createIndexDefinition;
+
+import java.util.function.Consumer;
+
+import org.apache.jackrabbit.oak.api.CommitFailedException;
+import org.apache.jackrabbit.oak.api.IllegalRepositoryStateException;
+import org.apache.jackrabbit.oak.composite.MountedNodeStore;
+import
org.apache.jackrabbit.oak.composite.checks.UniqueIndexNodeStoreChecker.Context;
+import org.apache.jackrabbit.oak.plugins.index.IndexUpdateProvider;
+import
org.apache.jackrabbit.oak.plugins.index.property.PropertyIndexEditorProvider;
+import org.apache.jackrabbit.oak.plugins.memory.MemoryNodeStore;
+import org.apache.jackrabbit.oak.plugins.tree.TreeFactory;
+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.mount.Mounts;
+import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+
+import com.google.common.collect.ImmutableSet;
+
+public class UniqueIndexNodeStoreCheckerTest {
+
+ @Rule
+ public final ExpectedException exception = ExpectedException.none();
+
+ private MountInfoProvider mip;
+
+ @Before
+ public void prepare() {
+ mip = Mounts.newBuilder().
+ readOnlyMount("libs", "/libs", "/libs2").
+ readOnlyMount("apps", "/apps", "/apps2").
+ build();
+ }
+
+ @Test
+ public void uuidConflict_twoStores() throws Exception {
+
+ MemoryNodeStore globalStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore = new MemoryNodeStore();
+
+ populateStore(globalStore, b -> b.child("first").setProperty("foo",
"bar"));
+ populateStore(mountedStore, b ->
b.child("libs").child("first").setProperty("foo", "bar"));
+
+ UniqueIndexNodeStoreChecker checker = new
UniqueIndexNodeStoreChecker();
+ Context ctx = checker.createContext(globalStore, mip);
+
+ exception.expect(IllegalRepositoryStateException.class);
+ exception.expectMessage("1 errors were found");
+ exception.expectMessage("clash for value bar: 'duplicate unique index
entry'");
+
+ ErrorHolder error = new ErrorHolder();
+ checker.check(new MountedNodeStore(mip.getMountByName("libs"),
mountedStore), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ error.end();
+ }
+
+ @Test
+ public void uuidConflict_threeStores() throws Exception {
+
+ MemoryNodeStore globalStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore2 = new MemoryNodeStore();
+
+ populateStore(globalStore, b -> b.child("first").setProperty("foo",
"bar"));
+ populateStore(globalStore, b -> b.child("second").setProperty("foo",
"baz"));
+ populateStore(mountedStore, b ->
b.child("libs").child("first").setProperty("foo", "bar"));
+ populateStore(mountedStore2, b ->
b.child("apps").child("first").setProperty("foo", "baz"));
+
+ UniqueIndexNodeStoreChecker checker = new
UniqueIndexNodeStoreChecker();
+ Context ctx = checker.createContext(globalStore, mip);
+
+ exception.expect(IllegalRepositoryStateException.class);
+ exception.expectMessage("2 errors were found");
+ exception.expectMessage("clash for value bar: 'duplicate unique index
entry'");
+ exception.expectMessage("clash for value baz: 'duplicate unique index
entry'");
+
+ ErrorHolder error = new ErrorHolder();
+ checker.check(new MountedNodeStore(mip.getMountByName("libs"),
mountedStore), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ checker.check(new MountedNodeStore(mip.getMountByName("apps"),
mountedStore2), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ error.end();
+ }
+
+ @Test
+ public void noConflict() throws Exception {
+
+ MemoryNodeStore globalStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore = new MemoryNodeStore();
+
+ populateStore(globalStore, b -> b.child("first").setProperty("foo",
"baz"));
+ populateStore(mountedStore, b ->
b.child("libs").child("first").setProperty("foo", "bar"));
+
+ UniqueIndexNodeStoreChecker checker = new
UniqueIndexNodeStoreChecker();
+ Context ctx = checker.createContext(globalStore, mip);
+
+ ErrorHolder error = new ErrorHolder();
+ checker.check(new MountedNodeStore(mip.getMountByName("libs"),
mountedStore), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ error.end();
+ }
+
+ /**
+ * Tests that if a mount has an index clash but the path does not belong
to the mount no error is reported
+ */
+ @Test
+ public void noConflict_mountHasDuplicateOutsideOfPath() throws Exception {
+
+ MemoryNodeStore globalStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore = new MemoryNodeStore();
+
+ populateStore(globalStore, b -> b.child("first").setProperty("foo",
"bar"));
+ populateStore(mountedStore, b -> b.child("second").setProperty("foo",
"bar"));
+
+ UniqueIndexNodeStoreChecker checker = new
UniqueIndexNodeStoreChecker();
+ Context ctx = checker.createContext(globalStore, mip);
+
+ ErrorHolder error = new ErrorHolder();
+ checker.check(new MountedNodeStore(mip.getMountByName("libs"),
mountedStore), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ error.end();
+ }
+
+ /**
+ * Tests that if a mount has an index clash but the path does not belog to
the global mount no error is reported
+ *
+ */
+ @Test
+ public void noConflict_globalMountHasDuplicateOutsideOfPath() throws
Exception {
+
+ MemoryNodeStore globalStore = new MemoryNodeStore();
+ MemoryNodeStore mountedStore = new MemoryNodeStore();
+
+ populateStore(globalStore, b ->
b.child("libs").child("first").setProperty("foo", "bar"));
+ populateStore(mountedStore, b ->
b.child("libs").child("second").setProperty("foo", "bar"));
+
+ UniqueIndexNodeStoreChecker checker = new
UniqueIndexNodeStoreChecker();
+ Context ctx = checker.createContext(globalStore, mip);
+
+ ErrorHolder error = new ErrorHolder();
+ checker.check(new MountedNodeStore(mip.getMountByName("libs"),
mountedStore), TreeFactory.createReadOnlyTree(mountedStore.getRoot()), error,
ctx);
+ error.end();
+ }
+
+ private void populateStore(NodeStore ns, Consumer<NodeBuilder> action)
throws CommitFailedException {
+
+ NodeBuilder builder = ns.getRoot().builder();
+ NodeBuilder index =
createIndexDefinition(builder.child(INDEX_DEFINITIONS_NAME), "foo",
+ true, true, ImmutableSet.of("foo"), null);
+ index.setProperty("entryCount", -1);
+
+ action.accept(builder);
+
+ ns.merge(builder,new EditorHook(new IndexUpdateProvider(
+ new PropertyIndexEditorProvider().with(mip))),
CommitInfo.EMPTY);
+ }
+}