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

org.apache.jackrabbit.oak.plugins.index.property.strategy.UniqueEntryStoreStrategy Maven / Gradle / Ivy

There is a newer version: 1.68.0
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.apache.jackrabbit.oak.plugins.index.property.strategy;

import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.ENTRY_COUNT_PROPERTY_NAME;
import static org.apache.jackrabbit.oak.plugins.index.IndexConstants.INDEX_CONTENT_NODE_NAME;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Set;
import java.util.function.Consumer;

import org.apache.jackrabbit.guava.common.base.Supplier;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.memory.MultiStringPropertyState;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.apache.jackrabbit.oak.spi.state.ChildNodeEntry;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.apache.jackrabbit.oak.plugins.index.counter.ApproximateCounter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * An IndexStoreStrategy implementation that saves the unique node in a single property.
* This should reduce the number of nodes in the repository, and speed up access.
*
* For example for a node that is under {@code /test/node}, the index * structure will be {@code /oak:index/index/@key}: */ public class UniqueEntryStoreStrategy implements IndexStoreStrategy { static final Logger LOG = LoggerFactory.getLogger(UniqueEntryStoreStrategy.class); private static final Consumer NOOP = (nb) -> {}; private final String indexName; private final Consumer insertCallback; public UniqueEntryStoreStrategy() { this(INDEX_CONTENT_NODE_NAME); } public UniqueEntryStoreStrategy(String indexName) { this(indexName, NOOP); } public UniqueEntryStoreStrategy(String indexName, @NotNull Consumer insertCallback) { this.indexName = indexName; this.insertCallback = insertCallback; } @Override public void update( Supplier index, String path, @Nullable final String indexName, @Nullable final NodeBuilder indexMeta, Set beforeKeys, Set afterKeys) { for (String key : beforeKeys) { remove(index.get(), key, path); } for (String key : afterKeys) { insert(index.get(), key, path); } } private static void remove(NodeBuilder index, String key, String value) { ApproximateCounter.adjustCountSync(index, -1); NodeBuilder builder = index.getChildNode(key); if (builder.exists()) { // there could be (temporarily) multiple entries // we need to remove the right one PropertyState s = builder.getProperty("entry"); if (s == null || s.count() == 1) { builder.remove(); } else { ArrayList list = new ArrayList(); for (int i = 0; i < s.count(); i++) { String r = s.getValue(Type.STRING, i); if (!r.equals(value)) { list.add(r); } } PropertyState s2 = MultiStringPropertyState.stringProperty("entry", list); builder.setProperty(s2); } } } private void insert(NodeBuilder index, String key, String value) { ApproximateCounter.adjustCountSync(index, 1); NodeBuilder k = index.child(key); ArrayList list = new ArrayList(); list.add(value); if (k.hasProperty("entry")) { // duplicate key (to detect duplicate entries) // this is just set temporarily, // while trying to add a duplicate entry PropertyState s = k.getProperty("entry"); for (int i = 0; i < s.count(); i++) { String r = s.getValue(Type.STRING, i); if (!list.contains(r)) { list.add(r); } } } PropertyState s2 = MultiStringPropertyState.stringProperty("entry", list); k.setProperty(s2); insertCallback.accept(k); } @Override public Iterable query(final Filter filter, final String indexName, final NodeState indexMeta, final Iterable values) { return query0(filter, indexName, indexMeta, values, new HitProducer() { @Override public String produce(NodeState indexHit, String pathName) { PropertyState s = indexHit.getProperty("entry"); if (s.count() <= 1) { return s.getValue(Type.STRING, 0); } else { StringBuilder buff = new StringBuilder(); for (int i = 0; i < s.count(); i++) { if (i > 0) { buff.append(", "); } buff.append(s.getValue(Type.STRING, i)); } return buff.toString(); } } }); } /** * Search for a given set of values, returning {@linkplain IndexEntry} results * * @param filter the filter (can optionally be used for optimized query execution) * @param indexName the name of the index (for logging) * @param indexMeta the index metadata node (may not be null) * @param values values to look for (null to check for property existence) * @return an iterator of index entries * * @throws UnsupportedOperationException if the operation is not supported */ public Iterable queryEntries(Filter filter, String indexName, NodeState indexMeta, Iterable values) { return query0(filter, indexName, indexMeta, values, new HitProducer() { @Override public IndexEntry produce(NodeState indexHit, String pathName) { PropertyState s = indexHit.getProperty("entry"); return new IndexEntry(s.getValue(Type.STRING, 0), pathName); } }); } private Iterable query0(Filter filter, String indexName, NodeState indexMeta, Iterable values, HitProducer prod) { final NodeState index = indexMeta.getChildNode(getIndexNodeName()); return new Iterable() { @Override public Iterator iterator() { if (values == null) { return new Iterator() { Iterator it = index.getChildNodeEntries().iterator(); @Override public boolean hasNext() { return it.hasNext(); } @Override public T next() { ChildNodeEntry indexEntry = it.next(); return prod.produce(indexEntry.getNodeState(), indexEntry.getName()); } @Override public void remove() { it.remove(); } }; } ArrayList list = new ArrayList<>(); for (String p : values) { NodeState key = index.getChildNode(p); if (key.exists()) { // we have an entry for this value, so use it list.add(prod.produce(key, p)); } } return list.iterator(); } }; } @Override public boolean exists(Supplier index, String key) { return index.get().hasChildNode(key); } @Override public long count(NodeState root, NodeState indexMeta, Set values, int max) { NodeState index = indexMeta.getChildNode(getIndexNodeName()); long count = 0; if (values == null) { PropertyState ec = indexMeta.getProperty(ENTRY_COUNT_PROPERTY_NAME); if (ec != null) { count = ec.getValue(Type.LONG); if (count >= 0) { return count; } } if (count == 0) { long approxCount = ApproximateCounter.getCountSync(index); if (approxCount != -1) { return approxCount; } } count = 1 + index.getChildNodeCount(max); // "is not null" queries typically read more data count *= 10; } else if (values.size() == 1) { NodeState k = index.getChildNode(values.iterator().next()); if (k.exists()) { count = k.getProperty("entry").count(); } else { count = 0; } } else { count = values.size(); } return count; } @Override 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; } /** * Creates a specific type of "hit" to return from the query methods * *

Use primarily to reduce duplication when the query algorithms execute mostly the same steps but return different objects.

* * @param The type of Hit to produce */ private interface HitProducer { /** * Invoked when a matching index entry is found * * @param indexHit the index node * @param propertyValue the value of the property * @return the value produced for the specific "hit" */ T produce(NodeState indexHit, String propertyValue); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy