/* * 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.core.query.lucene; import org.apache.jackrabbit.core.ItemManager; import org.apache.jackrabbit.core.NodeImpl; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.spi.Path; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import java.util.Arrays; import java.util.Comparator; import java.util.List; import java.util.ArrayList; /** * Implements a ScoreNodeIterator that returns the score nodes in document order. */ class DocOrderScoreNodeIterator implements ScoreNodeIterator { /** Logger instance for this class */ private static final Logger log = LoggerFactory.getLogger(DocOrderScoreNodeIterator.class); /** A node iterator with ordered nodes */ private ScoreNodeIterator orderedNodes; /** Unordered list of {@link ScoreNode}[]s. */ private final List<ScoreNode[]> scoreNodes; /** ItemManager to turn UUIDs into Node instances */ protected final ItemManager itemMgr; /** * Apply document order on the score nodes with this selectorIndex. */ private final int selectorIndex; /** * Creates a <code>DocOrderScoreNodeIterator</code> that orders the nodes in * <code>scoreNodes</code> in document order. * * @param itemMgr the item manager of the session executing the * query. * @param scoreNodes the ids of the matching nodes with their score * value. <code>List<ScoreNode[]></code> * @param selectorIndex apply document order on the score nodes with this * selectorIndex. */ DocOrderScoreNodeIterator(ItemManager itemMgr, List<ScoreNode[]> scoreNodes, int selectorIndex) { this.itemMgr = itemMgr; this.scoreNodes = scoreNodes; this.selectorIndex = selectorIndex; } /** * {@inheritDoc} */ public Object next() { return nextScoreNodes(); } /** * {@inheritDoc} */ public ScoreNode[] nextScoreNodes() { initOrderedIterator(); return orderedNodes.nextScoreNodes(); } /** * @throws UnsupportedOperationException always. */ public void remove() { throw new UnsupportedOperationException("remove"); } /** * {@inheritDoc} */ public void skip(long skipNum) { initOrderedIterator(); orderedNodes.skip(skipNum); } /** * Returns the number of nodes in this iterator. * </p> * Note: The number returned by this method may differ from the number * of nodes actually returned by calls to hasNext() / getNextNode()! This * is because this iterator works on a lazy instantiation basis and while * iterating over the nodes some of them might have been deleted in the * meantime. Those will not be returned by getNextNode(). As soon as an * invalid node is detected, the size of this iterator is adjusted. * * @return the number of node in this iterator. */ public long getSize() { if (orderedNodes != null) { return orderedNodes.getSize(); } else { return scoreNodes.size(); } } /** * {@inheritDoc} */ public long getPosition() { initOrderedIterator(); return orderedNodes.getPosition(); } /** * {@inheritDoc} */ public boolean hasNext() { initOrderedIterator(); return orderedNodes.hasNext(); } //------------------------< internal >-------------------------------------- /** * Initializes the NodeIterator in document order */ private void initOrderedIterator() { if (orderedNodes != null) { return; } long time = System.currentTimeMillis(); ScoreNode[][] nodes = scoreNodes.toArray(new ScoreNode[scoreNodes.size()][]); final List<NodeId> invalidIDs = new ArrayList<NodeId>(2); do { if (invalidIDs.size() > 0) { // previous sort run was not successful -> remove failed uuids List<ScoreNode[]> tmp = new ArrayList<ScoreNode[]>(); for (ScoreNode[] node : nodes) { if (!invalidIDs.contains(node[selectorIndex].getNodeId())) { tmp.add(node); } } nodes = tmp.toArray(new ScoreNode[tmp.size()][]); invalidIDs.clear(); } try { // sort the uuids Arrays.sort(nodes, new Comparator<ScoreNode[]>() { public int compare(ScoreNode[] o1, ScoreNode[] o2) { ScoreNode n1 = o1[selectorIndex]; ScoreNode n2 = o2[selectorIndex]; // handle null values // null is considered less than any value if (n1 == n2) { return 0; } else if (n1 == null) { return -1; } else if (n2 == null) { return 1; } try { NodeImpl node1; try { node1 = (NodeImpl) itemMgr.getItem(n1.getNodeId()); } catch (RepositoryException e) { log.warn("Node " + n1.getNodeId() + " does not exist anymore: " + e); // node does not exist anymore invalidIDs.add(n1.getNodeId()); SortFailedException sfe = new SortFailedException(); sfe.initCause(e); throw sfe; } NodeImpl node2; try { node2 = (NodeImpl) itemMgr.getItem(n2.getNodeId()); } catch (RepositoryException e) { log.warn("Node " + n2.getNodeId() + " does not exist anymore: " + e); // node does not exist anymore invalidIDs.add(n2.getNodeId()); SortFailedException sfe = new SortFailedException(); sfe.initCause(e); throw sfe; } Path.Element[] path1 = node1.getPrimaryPath().getElements(); Path.Element[] path2 = node2.getPrimaryPath().getElements(); // find nearest common ancestor int commonDepth = 0; // root while (path1.length > commonDepth && path2.length > commonDepth) { if (path1[commonDepth].equals(path2[commonDepth])) { commonDepth++; } else { break; } } // path elements at last depth were equal commonDepth--; // check if either path is an ancestor of the other if (path1.length - 1 == commonDepth) { // path1 itself is ancestor of path2 return -1; } if (path2.length - 1 == commonDepth) { // path2 itself is ancestor of path1 return 1; } // get common ancestor node NodeImpl commonNode = (NodeImpl) node1.getAncestor(commonDepth); // move node1/node2 to the commonDepth + 1 // node1 and node2 then will be child nodes of commonNode node1 = (NodeImpl) node1.getAncestor(commonDepth + 1); node2 = (NodeImpl) node2.getAncestor(commonDepth + 1); for (NodeIterator it = commonNode.getNodes(); it.hasNext();) { Node child = it.nextNode(); if (child.isSame(node1)) { return -1; } else if (child.isSame(node2)) { return 1; } } log.error("Internal error: unable to determine document order of nodes:"); log.error("\tNode1: " + node1.getPath()); log.error("\tNode2: " + node2.getPath()); } catch (RepositoryException e) { log.error("Exception while sorting nodes in document order: " + e.toString(), e); } // if we get here something went wrong // remove both uuids from array invalidIDs.add(n1.getNodeId()); invalidIDs.add(n2.getNodeId()); // terminate sorting throw new SortFailedException(); } }); } catch (SortFailedException e) { // retry } } while (invalidIDs.size() > 0); if (log.isDebugEnabled()) { log.debug("" + nodes.length + " node(s) ordered in " + (System.currentTimeMillis() - time) + " ms"); } orderedNodes = new ScoreNodeIteratorImpl(nodes); } /** * Indicates that sorting failed. */ @SuppressWarnings("serial") private static final class SortFailedException extends RuntimeException { } }