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

org.apache.jackrabbit.commons.flat.BTreeManager 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.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", "unchecked" }) protected SizedIterator getNodes(Node node) throws RepositoryException { NodeIterator nodes = node.getNodes(); return getSizedIterator(nodes, nodes.getSize()); } /** * Returns a {@link SizedIterator} of the properties of node * which excludes the jcr.primaryType property. */ @SuppressWarnings({ "deprecation", "unchecked" }) 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(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 >--- 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; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy