/* * 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; import java.util.ArrayList; import java.util.List; import java.util.NoSuchElementException; import javax.jcr.AccessDeniedException; import javax.jcr.Item; import javax.jcr.ItemNotFoundException; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.Property; import javax.jcr.PropertyIterator; import javax.jcr.RepositoryException; import org.apache.jackrabbit.core.id.ItemId; import org.apache.jackrabbit.core.id.NodeId; import org.apache.jackrabbit.core.session.SessionContext; import org.apache.jackrabbit.core.state.ItemStateManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * <code>LazyItemIterator</code> is an id-based iterator that instantiates * the <code>Item</code>s only when they are requested. * <p> * <strong>Important:</strong> <code>Item</code>s that appear to be nonexistent * for some reason (e.g. because of insufficient access rights or because they * have been removed since the iterator has been retrieved) are silently * skipped. As a result the size of the iterator as reported by * {@link #getSize()} might appear to be shrinking while iterating over the * items. * todo should getSize() better always return -1? * * @see #getSize() */ public class LazyItemIterator implements NodeIterator, PropertyIterator { /** Logger instance for this class */ private static Logger log = LoggerFactory.getLogger(LazyItemIterator.class); /** * The session context used to access the repository. */ private final SessionContext sessionContext; /** the item manager that is used to lazily fetch the items */ private final ItemManager itemMgr; /** the list of item ids */ private final List<ItemId> idList; /** parent node id (when returning children nodes) or <code>null</code> */ private final NodeId parentId; /** the position of the next item */ private int pos; /** prefetched item to be returned on <code>{@link #next()}</code> */ private Item next; /** * Creates a new <code>LazyItemIterator</code> instance. * * @param sessionContext session context * @param idList list of item id's */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList) { this(sessionContext, idList, null); } /** * Creates a new <code>LazyItemIterator</code> instance, additionally taking * a parent id as parameter. This version should be invoked to strictly return * children nodes of a node. * * @param sessionContext session context * @param idList list of item id's * @param parentId parent id. */ public LazyItemIterator(SessionContext sessionContext, List< ? extends ItemId> idList, NodeId parentId) { this.sessionContext = sessionContext; this.itemMgr = sessionContext.getSessionImpl().getItemManager(); this.idList = new ArrayList<ItemId>(idList); this.parentId = parentId; // prefetch first item pos = 0; prefetchNext(); } /** * Prefetches next item. * <p> * {@link #next} is set to the next available item in this iterator or to * <code>null</code> in case there are no more items. */ private void prefetchNext() { // reset next = null; while (next == null && pos < idList.size()) { ItemId id = idList.get(pos); try { if (parentId != null) { next = itemMgr.getNode((NodeId) id, parentId); } else { next = itemMgr.getItem(id); } } catch (ItemNotFoundException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // maybe fix the root cause if (parentId != null && sessionContext.getSessionImpl().autoFixCorruptions()) { try { // it might be an access right problem // we need to check if the item doesn't exist in the ism ItemStateManager ism = sessionContext.getItemStateManager(); if (!ism.hasItemState(id)) { NodeImpl p = (NodeImpl) itemMgr.getItem(parentId); p.removeChildNode((NodeId) id); p.save(); } } catch (RepositoryException e2) { log.error("could not fix repository inconsistency", e); // ignore } } // try next } catch (AccessDeniedException e) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); // try next } catch (RepositoryException e) { log.error("failed to fetch item " + id + ", skipping...", e); // remove invalid id idList.remove(pos); // try next } } } //---------------------------------------------------------< NodeIterator > /** * {@inheritDoc} */ public Node nextNode() { return (Node) next(); } //-----------------------------------------------------< PropertyIterator > /** * {@inheritDoc} */ public Property nextProperty() { return (Property) next(); } //--------------------------------------------------------< RangeIterator > /** * {@inheritDoc} */ public long getPosition() { return pos; } /** * {@inheritDoc} * <p> * Note that the size of the iterator as reported by {@link #getSize()} * might appear to be shrinking while iterating because items that for * some reason cannot be retrieved through this iterator are silently * skipped, thus reducing the size of this iterator. * * todo better to always return -1? */ public long getSize() { return idList.size(); } /** * {@inheritDoc} */ public void skip(long skipNum) { if (skipNum < 0) { throw new IllegalArgumentException("skipNum must not be negative"); } if (skipNum == 0) { return; } if (next == null) { throw new NoSuchElementException(); } // reset next = null; // skip the first (skipNum - 1) items without actually retrieving them while (--skipNum > 0) { pos++; if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } ItemId id = idList.get(pos); // eliminate invalid items from this iterator while (!itemMgr.itemExists(id)) { log.debug("ignoring nonexistent item " + id); // remove invalid id idList.remove(pos); if (pos >= idList.size()) { // skipped past last item throw new NoSuchElementException(); } id = idList.get(pos); } } // prefetch final item (the one to be returned on next()) pos++; prefetchNext(); } //-------------------------------------------------------------< Iterator > /** * {@inheritDoc} */ public boolean hasNext() { return next != null; } /** * {@inheritDoc} */ public Object next() { if (next == null) { throw new NoSuchElementException(); } Item item = next; pos++; prefetchNext(); return item; } /** * {@inheritDoc} * * @throws UnsupportedOperationException always since not implemented */ public void remove() { throw new UnsupportedOperationException("remove"); } }