blob: 7df2e6f34cc9d0411700123870905828d248d192 [file] [log] [blame]
/*
* 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.counter;
import org.apache.jackrabbit.oak.api.CommitFailedException;
import org.apache.jackrabbit.oak.api.PropertyState;
import org.apache.jackrabbit.oak.api.Type;
import org.apache.jackrabbit.oak.plugins.index.IndexUpdateCallback;
import org.apache.jackrabbit.oak.plugins.index.counter.jmx.NodeCounter;
import org.apache.jackrabbit.oak.spi.commit.Editor;
import org.apache.jackrabbit.oak.spi.state.NodeBuilder;
import org.apache.jackrabbit.oak.spi.state.NodeState;
import org.jetbrains.annotations.Nullable;
/**
* An approximate descendant node counter mechanism.
*/
@Deprecated
public class NodeCounterEditorOld implements Editor {
public static final String DATA_NODE_NAME = ":index";
// the property that is used with the "old" (pseudo-random number generator based) method
public static final String COUNT_PROPERTY_NAME = ":count";
// the property that is used with the "new" (hash of the path based) method
public static final String COUNT_HASH_PROPERTY_NAME = ":cnt";
public static final int DEFAULT_RESOLUTION = 1000;
private final NodeCounterRoot root;
private final NodeCounterEditorOld parent;
private final String name;
private long countOffset;
private SipHash hash;
public NodeCounterEditorOld(NodeCounterRoot root, NodeCounterEditorOld parent, String name, SipHash hash) {
this.parent = parent;
this.root = root;
this.name = name;
this.hash = hash;
}
private SipHash getHash() {
if (hash != null) {
return hash;
}
SipHash h;
if (parent == null) {
h = new SipHash(root.seed);
} else {
h = new SipHash(parent.getHash(), name.hashCode());
}
this.hash = h;
return h;
}
@Override
public void enter(NodeState before, NodeState after)
throws CommitFailedException {
// nothing to do
}
@Override
public void leave(NodeState before, NodeState after)
throws CommitFailedException {
if (NodeCounter.COUNT_HASH) {
leaveNew(before, after);
return;
}
leaveOld(before, after);
}
private void leaveOld(NodeState before, NodeState after)
throws CommitFailedException {
long offset = ApproximateCounter.calculateOffset(
countOffset, root.resolution);
if (offset == 0) {
return;
}
// only read the value of the property if really needed
NodeBuilder builder = getBuilder();
PropertyState p = builder.getProperty(COUNT_PROPERTY_NAME);
long count = p == null ? 0 : p.getValue(Type.LONG);
offset = ApproximateCounter.adjustOffset(count,
offset, root.resolution);
if (offset == 0) {
return;
}
count += offset;
root.callback.indexUpdate();
if (count == 0) {
if (builder.getChildNodeCount(1) >= 0) {
builder.removeProperty(COUNT_PROPERTY_NAME);
} else {
builder.remove();
}
} else {
builder.setProperty(COUNT_PROPERTY_NAME, count);
}
}
public void leaveNew(NodeState before, NodeState after)
throws CommitFailedException {
if (countOffset == 0) {
return;
}
NodeBuilder builder = getBuilder();
PropertyState p = builder.getProperty(COUNT_HASH_PROPERTY_NAME);
long count = p == null ? 0 : p.getValue(Type.LONG);
count += countOffset;
root.callback.indexUpdate();
if (count <= 0) {
if (builder.getChildNodeCount(1) >= 0) {
builder.removeProperty(COUNT_HASH_PROPERTY_NAME);
} else {
builder.remove();
}
} else {
builder.setProperty(COUNT_HASH_PROPERTY_NAME, count);
}
}
private NodeBuilder getBuilder() {
if (parent == null) {
return root.definition.child(DATA_NODE_NAME);
}
return parent.getBuilder().child(name);
}
@Override
public void propertyAdded(PropertyState after) throws CommitFailedException {
// nothing to do
}
@Override
public void propertyChanged(PropertyState before, PropertyState after)
throws CommitFailedException {
// nothing to do
}
@Override
public void propertyDeleted(PropertyState before)
throws CommitFailedException {
// nothing to do
}
@Override
@Nullable
public Editor childNodeChanged(String name, NodeState before, NodeState after)
throws CommitFailedException {
return getChildIndexEditor(name, null);
}
@Override
@Nullable
public Editor childNodeAdded(String name, NodeState after)
throws CommitFailedException {
if (NodeCounter.COUNT_HASH) {
SipHash h = new SipHash(getHash(), name.hashCode());
// with bitMask=1024: with a probability of 1:1024,
if ((h.hashCode() & root.bitMask) == 0) {
// add 1024
count(root.bitMask + 1);
}
return getChildIndexEditor(name, h);
}
count(1);
return getChildIndexEditor(name, null);
}
@Override
@Nullable
public Editor childNodeDeleted(String name, NodeState before)
throws CommitFailedException {
if (NodeCounter.COUNT_HASH) {
SipHash h = new SipHash(getHash(), name.hashCode());
// with bitMask=1024: with a probability of 1:1024,
if ((h.hashCode() & root.bitMask) == 0) {
// subtract 1024
count(-(root.bitMask + 1));
}
return getChildIndexEditor(name, h);
}
count(-1);
return getChildIndexEditor(name, null);
}
private void count(int offset) {
countOffset += offset;
if (parent != null) {
parent.count(offset);
}
}
private Editor getChildIndexEditor(String name, SipHash hash) {
return new NodeCounterEditorOld(root, this, name, hash);
}
public static class NodeCounterRoot {
final int resolution;
final long seed;
final int bitMask;
final NodeBuilder definition;
final NodeState root;
final IndexUpdateCallback callback;
NodeCounterRoot(int resolution, long seed, NodeBuilder definition, NodeState root, IndexUpdateCallback callback) {
this.resolution = resolution;
this.seed = seed;
// if resolution is 1000, then the bitMask is 1023 (bits 0..9 set)
this.bitMask = (Integer.highestOneBit(resolution) * 2) - 1;
this.definition = definition;
this.root = root;
this.callback = callback;
}
}
}