All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.jackrabbit.oak.composite.checks.UniqueIndexNodeStoreChecker Maven / Gradle / Ivy

/*
 * 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.osgi.service.component.annotations.Component;
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 org.apache.jackrabbit.guava.common.collect.Maps;

/**
 * Checker that ensures the consistency of unique entries in the various mounts
 * 
 * 

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.

* *

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.

* */ @Component(service = {MountedNodeStoreChecker.class}) public class UniqueIndexNodeStoreChecker implements MountedNodeStoreChecker { 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, this); 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, this); } } } // execute checks context.runChecks(context, errorHolder); return false; } static class Context { private final MountInfoProvider mip; private final Map combinations = new HashMap<>(); private final Map 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, UniqueIndexNodeStoreChecker checker) { IndexCombination combination = combinations.get(rootIndexDef.getName()); if ( combination == null ) { combination = new IndexCombination(rootIndexDef, checker); 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 UniqueIndexNodeStoreChecker checker; private final Map indexEntries = Maps.newHashMap(); private final List checked = new ArrayList<>(); // bounded as the ErrorHolder has a reasonable limit of entries before it fails immediately private final Set reportedConflictingValues = new HashSet<>(); IndexCombination(ChildNodeEntry rootIndexDef, UniqueIndexNodeStoreChecker checker) { this.checker = checker; 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 indexEntry : indexEntries.entrySet() ) { for ( Map.Entry 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 indexEntry, Entry 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 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 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", checker); } } } } private StrategyWrapper getWrapper(Entry 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 { 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 queryAll() { return strategy.queryEntries(Filter.EMPTY_FILTER, indexName, indexNode, null); } public Optional queryOne(String value) { Iterable results = strategy.queryEntries(Filter.EMPTY_FILTER, indexName, indexNode, Collections.singleton(value)); if ( !results.iterator().hasNext() ) { return Optional.empty(); } return Optional.of(results.iterator().next()); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy