org.apache.jackrabbit.commons.flat.BTreeManager Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
The 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.commons.flat;
import org.apache.jackrabbit.JcrConstants;
import org.apache.jackrabbit.commons.iterator.FilterIterator;
import org.apache.jackrabbit.commons.predicate.Predicate;
import javax.jcr.Item;
import javax.jcr.Node;
import javax.jcr.NodeIterator;
import javax.jcr.Property;
import javax.jcr.PropertyIterator;
import javax.jcr.RepositoryException;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* This {@link TreeManager} implementation provides B+-tree like behavior. That
* is items of a sequence (i.e. {@link NodeSequence} or {@link PropertySequence})
* are mapped to a sub-tree in JCR in a way such that only leave nodes carry
* actual values, the sub-tree is always balanced and ordered. This
* implementation does in contrast to a full B+-tree implementation not
* join nodes after deletions. This does not affect the order of items and also
* leaves the tree balanced wrt. its depths. It might however result in a sparse
* tree. That is, the tree might get unbalanced wrt. its weights.
*
* The nodes in the JCR sub tree are arranged such that any node named x
* only contains child nodes with names greater or equal to x
.
* The implementation keeps the child nodes in the sub tree ordered if the
* respective node type supports ordering of child nodes.
* Ordering is always wrt. to a {@link Comparator} on the respective keys.
* For lexical order this arrangement corresponds to how words are arranged in a multi
* volume encyclopedia.
*
* Example usage:
*
* // Create a new TreeManager instance rooted at node. Splitting of nodes takes place
* // when the number of children of a node exceeds 40 and is done such that each new
* // node has at least 40 child nodes. The keys are ordered according to the natural
* // order of java.lang.String.
* TreeManager treeManager = new BTreeManager(node, 20, 40, Rank.<String>comparableComparator(), true);
*
* // Create a new NodeSequence with that tree manager
* NodeSequence nodes = ItemSequence.createNodeSequence(treeManager);
*
* // Add nodes with key "jcr" and "day"
* nodes.addNode("jcr", NodeType.NT_UNSTRUCTURED);
* nodes.addNode("day", NodeType.NT_UNSTRUCTURED);
*
* // Iterate over the node in the sequence.
* // Prints "day jcr "
* for (Node n : nodes) {
* System.out.print(n.getName() + " ");
* }
*
* // Retrieve node with key "jcr"
* Node n = nodes.getItem("jcr");
*
* // Remove node with key "day"
* nodes.removeNode("day");
*
*/
public class BTreeManager implements TreeManager {
private final Node root;
private final int minChildren;
private final int maxChildren;
private final Comparator order;
private final boolean autoSave;
private final Comparator- itemOrder;
private final Set
ignoredProperties = new HashSet(Arrays.asList(
JcrConstants.JCR_PRIMARYTYPE,
JcrConstants.JCR_MIXINTYPES));
/**
* Create a new {@link BTreeManager} rooted at Node root
.
*
* @param root the root of the JCR sub-tree where the items of the sequence
* are stored.
* @param minChildren minimal number of children for a node after splitting.
* @param maxChildren maximal number of children for a node after which
* splitting occurs.
* @param order order according to which the keys are stored
* @param autoSave determines whether the current session is saved after
* add/delete operations.
* @throws RepositoryException
*/
public BTreeManager(Node root, int minChildren, int maxChildren, Comparator order, boolean autoSave)
throws RepositoryException {
super();
if (root == null) {
throw new IllegalArgumentException("root must not be null");
}
if (minChildren <= 0) {
throw new IllegalArgumentException("minChildren must be positive");
}
if (2 * minChildren > maxChildren) {
throw new IllegalArgumentException("maxChildren must be at least twice minChildren");
}
if (order == null) {
throw new IllegalArgumentException("order must not be null");
}
this.root = root;
this.minChildren = minChildren;
this.maxChildren = maxChildren;
this.order = order;
this.autoSave = autoSave;
this.itemOrder = new Comparator- () {
public int compare(Item i1, Item i2) {
try {
return BTreeManager.this.order.compare(i1.getName(), i2.getName());
}
catch (RepositoryException e) {
throw new WrappedRepositoryException(e);
}
}
};
}
/**
* Properties to ignore. The default set contains {@link JcrConstants#JCR_PRIMARYTYPE}
* and {@link JcrConstants#JCR_MIXINTYPES}.
*
* @return
*/
public Set
getIgnoredProperties() {
return ignoredProperties;
}
/**
* This implementations splits node
when its number of child
* nodes exceeds the maximum number specified in the constructor. Splitting
* is done such that after the split each of the new child nodes contains at
* least as many nodes as specified in the constructor.
*
* @see org.apache.jackrabbit.commons.flat.TreeManager#split(org.apache.jackrabbit.commons.flat.ItemSequence,
* javax.jcr.Node, javax.jcr.Node)
*/
@SuppressWarnings("deprecation")
public void split(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException {
SizedIterator childNodes = getNodes(node);
int count = (int) childNodes.getSize();
if (count >= 0 && count <= maxChildren) {
return;
}
split(node, new Rank(childNodes, Node.class, count, itemOrder), itemSequence);
}
/**
* This implementations splits node
when its number of
* properties exceeds the maximum number specified in the constructor.
* Splitting is done such that after the split each of the new child nodes
* contains at least as many nodes as specified in the constructor.
*
* @see org.apache.jackrabbit.commons.flat.TreeManager#split(org.apache.jackrabbit.commons.flat.ItemSequence,
* javax.jcr.Node, javax.jcr.Property)
*/
@SuppressWarnings("deprecation")
public void split(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException {
SizedIterator properties = getProperties(node);
int count = (int) properties.getSize();
if (count >= 0 && count <= maxChildren) {
return;
}
split(node, new Rank(properties, Property.class, count, itemOrder), itemSequence);
}
/**
* This implementation does not actually join any nodes. It does however
* delete node
if {@link #getNodes(Node)} returns an empty
* iterator. It does further recursively delete any parent of
* node
which does not have any child node.
*
* @see org.apache.jackrabbit.commons.flat.TreeManager#join(org.apache.jackrabbit.commons.flat.ItemSequence,
* javax.jcr.Node, javax.jcr.Node)
*/
@SuppressWarnings("deprecation")
public void join(ItemSequence itemSequence, Node node, Node cause) throws RepositoryException {
SizedIterator nodes = getNodes(node);
long count = nodes.getSize();
if (count < 0) {
for (count = 0; nodes.hasNext(); count++) {
nodes.next();
}
}
if (count == 0) {
removeRec(node);
}
}
/**
* This implementation does not actually join any nodes. It does however
* delete node
if {@link #getProperties(Node)} returns an empty
* iterator. It does further recursively delete any parent of
* node
which does not have any child node.
*
* @see org.apache.jackrabbit.commons.flat.TreeManager#join(org.apache.jackrabbit.commons.flat.ItemSequence,
* javax.jcr.Node, javax.jcr.Property)
*/
@SuppressWarnings("deprecation")
public void join(ItemSequence itemSequence, Node node, Property cause) throws RepositoryException {
SizedIterator properties = getProperties(node);
long count = properties.getSize();
if (count < 0) {
for (count = 0; properties.hasNext(); count++) {
properties.next();
}
}
if (count == 0) {
removeRec(node);
}
}
public Node getRoot() {
return root;
}
public boolean isRoot(Node node) throws RepositoryException {
return node.isSame(root);
}
/**
* Returns !node.hasNodes()
* @see org.apache.jackrabbit.commons.flat.TreeManager#isLeaf(javax.jcr.Node)
*/
public boolean isLeaf(Node node) throws RepositoryException {
return !node.hasNodes();
}
public Comparator getOrder() {
return order;
}
public boolean getAutoSave() {
return autoSave;
}
// -----------------------------------------------------< internal >---
/**
* Returns a {@link SizedIterator} of the child nodes of node
.
*/
@SuppressWarnings("deprecation")
protected SizedIterator getNodes(Node node) throws RepositoryException {
NodeIterator nodes = node.getNodes();
return getSizedIterator(convert(nodes), nodes.getSize());
}
/**
* Returns a {@link SizedIterator} of the properties of node
* which excludes the jcr.primaryType
property.
*/
@SuppressWarnings("deprecation")
protected SizedIterator getProperties(Node node) throws RepositoryException {
final PropertyIterator properties = node.getProperties();
long size = properties.getSize();
for (Iterator ignored = ignoredProperties.iterator(); size > 0 && ignored.hasNext(); ) {
if (node.hasProperty(ignored.next())) {
size--;
}
}
return getSizedIterator(filterProperties(convert(properties)), size);
}
/**
* Creates and return an intermediate node for the given name
* as child node of parent
.
*/
protected Node createIntermediateNode(Node parent, String name) throws RepositoryException {
return parent.addNode(name);
}
/**
* Move node
to the new parent
.
*/
protected void move(Node node, Node parent) throws RepositoryException {
String oldPath = node.getPath();
String newPath = parent.getPath() + "/" + node.getName();
node.getSession().move(oldPath, newPath);
}
/**
* Move property
to the new parent
.
*/
protected void move(Property property, Node parent) throws RepositoryException {
parent.setProperty(property.getName(), property.getValue());
property.remove();
}
/**
* Wraps iterator
into a {@link SizedIterator} given a
* size
. The value of the size
parameter must
* correctly reflect the number of items in iterator
.
*/
@SuppressWarnings("deprecation")
protected final SizedIterator getSizedIterator(final Iterator iterator, final long size) {
return new SizedIterator() {
public boolean hasNext() {
return iterator.hasNext();
}
public T next() {
return iterator.next();
}
public void remove() {
iterator.remove();
}
public long getSize() {
return size;
}
};
}
// -----------------------------------------------------< internal >---
@SuppressWarnings("unchecked")
private static Iterator convert(PropertyIterator it) {
return it;
}
@SuppressWarnings("unchecked")
private static Iterator convert(NodeIterator it) {
return it;
}
private void split(Node node, Rank ranking, ItemSequence itemSequence) throws RepositoryException {
if (ranking.size() <= maxChildren) {
return;
}
try {
Node grandParent;
if (isRoot(node)) {
grandParent = node;
}
else {
grandParent = node.getParent();
// leave first minChildren items where they are
ranking.take(minChildren);
}
// move remaining items to new parents
for (int k = ranking.size() / minChildren; k > 0; k--) {
T item = ranking.take(1).next();
String key = item.getName();
Node newParent;
if (grandParent.getPrimaryNodeType().hasOrderableChildNodes()) {
Node dest = itemSequence.getSuccessor(grandParent, key);
newParent = createIntermediateNode(grandParent, key);
grandParent.orderBefore(key, dest == null ? null : dest.getName());
}
else {
newParent = createIntermediateNode(grandParent, key);
}
move(item, newParent);
int c = k > 1 ? minChildren - 1 : ranking.size();
Iterator remaining = ranking.take(c);
// If ordered, ranking returns an ordered iterator. So order will be correct here
while (remaining.hasNext()) {
move(remaining.next(), newParent);
}
}
// If we did not reach root yet, recursively split the parent
if (!node.isSame(root)) {
split(itemSequence, grandParent, (Node) null);
}
}
catch (WrappedRepositoryException e) {
throw e.wrapped();
}
}
private void move(T item, Node parent) throws RepositoryException {
if (item.isNode()) {
move((Node) item, parent);
}
else {
move((Property) item, parent);
}
}
private void removeRec(Node node) throws RepositoryException {
Node n = node;
while (!n.hasNodes() && !isRoot(n)) {
Node d = n;
n = n.getParent();
d.remove();
}
}
/**
* Filtering ignored properties from the given properties.
*/
private Iterator filterProperties(Iterator properties) {
return new FilterIterator(properties, new Predicate() {
public boolean evaluate(Object object) {
try {
Property p = (Property) object;
return !ignoredProperties.contains(p.getName());
}
catch (RepositoryException ignore) {
return true;
}
}
});
}
private static class WrappedRepositoryException extends RuntimeException {
private final RepositoryException wrapped;
public WrappedRepositoryException(RepositoryException e) {
super();
this.wrapped = e;
}
public RepositoryException wrapped() {
return wrapped;
}
}
}