Package org.apache.jackrabbit.core

Source Code of org.apache.jackrabbit.core.CachingHierarchyManager$LRUEntry

/*
* 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 org.apache.commons.collections.map.ReferenceMap;
import org.apache.jackrabbit.core.state.ItemState;
import org.apache.jackrabbit.core.state.ItemStateException;
import org.apache.jackrabbit.core.state.ItemStateManager;
import org.apache.jackrabbit.core.state.NodeState;
import org.apache.jackrabbit.core.state.NodeStateListener;
import org.apache.jackrabbit.core.util.Dumpable;
import org.apache.jackrabbit.name.MalformedPathException;
import org.apache.jackrabbit.name.NamespaceResolver;
import org.apache.jackrabbit.name.Path;
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.util.PathMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.io.PrintStream;

import javax.jcr.ItemNotFoundException;
import javax.jcr.PathNotFoundException;
import javax.jcr.RepositoryException;

/**
* Implementation of a <code>HierarchyManager</code> that caches paths of
* items.
*/
public class CachingHierarchyManager extends HierarchyManagerImpl
        implements NodeStateListener, Dumpable {

    /**
     * Default upper limit of cached states
     */
    public static final int DEFAULT_UPPER_LIMIT = 10000;

    /**
     * Logger instance
     */
    private static Logger log = LoggerFactory.getLogger(CachingHierarchyManager.class);

    /**
     * Mapping of paths to children in the path map
     */
    private final PathMap pathCache = new PathMap();

    /**
     * Mapping of item ids to <code>LRUEntry</code> in the path map
     */
    private final ReferenceMap idCache = new ReferenceMap(ReferenceMap.HARD, ReferenceMap.HARD);

    /**
     * Set of items that were moved
     */
    private final Set movedIds = new HashSet();

    /**
     * Cache monitor object
     */
    private final Object cacheMonitor = new Object();

    /**
     * Upper limit
     */
    private final int upperLimit;

    /**
     * Head of LRU
     */
    private LRUEntry head;

    /**
     * Tail of LRU
     */
    private LRUEntry tail;

    /**
     * Create a new instance of this class.
     *
     * @param rootNodeId   root node id
     * @param provider     item state manager
     * @param nsResolver   namespace resolver
     */
    public CachingHierarchyManager(NodeId rootNodeId,
                                   ItemStateManager provider,
                                   NamespaceResolver nsResolver) {
        super(rootNodeId, provider, nsResolver);
        upperLimit = DEFAULT_UPPER_LIMIT;
    }

    //-------------------------------------------------< base class overrides >
    /**
     * {@inheritDoc}
     * <p/>
     * Cache the intermediate item inside our cache.
     */
    protected ItemId resolvePath(Path path, ItemState state, int next)
            throws ItemStateException {

        if (state.isNode() && !isCached(state.getId())) {
            try {
                Path.PathBuilder builder = new Path.PathBuilder();
                Path.PathElement[] elements = path.getElements();
                for (int i = 0; i < next; i++) {
                    builder.addLast(elements[i]);
                }
                Path parentPath = builder.getPath();
                cache((NodeState) state, parentPath);
            } catch (MalformedPathException mpe) {
                log.warn("Failed to build path of " + state.getId(), mpe);
            }
        }
        return super.resolvePath(path, state, next);
    }

    /**
     * {@inheritDoc}
     * <p/>
     * Overridden method tries to find a mapping for the intermediate item
     * <code>state</code> and add its path elements to the builder currently
     * being used. If no mapping is found, the item is cached instead after
     * the base implementation has been invoked.
     */
    protected void buildPath(Path.PathBuilder builder, ItemState state)
            throws ItemStateException, RepositoryException {

        if (state.isNode()) {
            PathMap.Element element = get(state.getId());
            if (element != null) {
                try {
                    Path.PathElement[] elements = element.getPath().getElements();
                    for (int i = elements.length - 1; i >= 0; i--) {
                        builder.addFirst(elements[i]);
                    }
                    return;
                } catch (MalformedPathException mpe) {
                    String msg = "Failed to build path of " + state.getId();
                    log.debug(msg);
                    throw new RepositoryException(msg, mpe);
                }
            }
        }

        super.buildPath(builder, state);

        if (state.isNode()) {
            try {
                cache((NodeState) state, builder.getPath());
            } catch (MalformedPathException mpe) {
                log.warn("Failed to build path of " + state.getId());
            }
        }
    }

    //-----------------------------------------------------< HierarchyManager >
    /**
     * {@inheritDoc}
     * <p/>
     * Check the path indicated inside our cache first.
     */
    public ItemId resolvePath(Path path) throws RepositoryException {

        // Run base class shortcut and sanity checks first
        if (path.denotesRoot()) {
            return rootNodeId;
        } else if (!path.isCanonical()) {
            String msg = "path is not canonical";
            log.debug(msg);
            throw new RepositoryException(msg);
        }

        PathMap.Element element = map(path);
        if (element == null) {
            return super.resolvePath(path);
        }
        LRUEntry entry = (LRUEntry) element.get();
        if (element.hasPath(path)) {
            entry.touch();
            return entry.getId();
        }
        return super.resolvePath(path, entry.getId(), element.getDepth() + 1);
    }

    /**
     * {@inheritDoc}
     * <p/>
     * Overridden method simply checks whether we have an item matching the id
     * and returns its path, otherwise calls base implementation.
     */
    public Path getPath(ItemId id)
            throws ItemNotFoundException, RepositoryException {

        if (id.denotesNode()) {
            PathMap.Element element = get(id);
            if (element != null) {
                try {
                    return element.getPath();
                } catch (MalformedPathException mpe) {
                    String msg = "Failed to build path of " + id;
                    log.debug(msg);
                    throw new RepositoryException(msg, mpe);
                }
            }
        }
        return super.getPath(id);
    }

    /**
     * {@inheritDoc}
     */
    public QName getName(ItemId id)
            throws ItemNotFoundException, RepositoryException {

        if (id.denotesNode()) {
            PathMap.Element element = get(id);
            if (element != null) {
                return element.getName();
            }
        }
        return super.getName(id);
    }

    /**
     * {@inheritDoc}
     */
    public int getDepth(ItemId id)
            throws ItemNotFoundException, RepositoryException {

        if (id.denotesNode()) {
            PathMap.Element element = get(id);
            if (element != null) {
                return element.getDepth();
            }
        }
        return super.getDepth(id);
    }

    /**
     * {@inheritDoc}
     */
    public boolean isAncestor(NodeId nodeId, ItemId itemId)
            throws ItemNotFoundException, RepositoryException {

        if (itemId.denotesNode()) {
            PathMap.Element element = get(nodeId);
            if (element != null) {
                PathMap.Element child = get(itemId);
                if (child != null) {
                    return element.isAncestorOf(child);
                }
            }
        }
        return super.isAncestor(nodeId, itemId);
    }

    //----------------------------------------------------< ItemStateListener >

    /**
     * {@inheritDoc}
     */
    public void stateCreated(ItemState created) {
    }

    /**
     * {@inheritDoc}
     */
    public void stateModified(ItemState modified) {
        if (modified.isNode()) {
            nodeModified((NodeState) modified);
        }
    }

    /**
     * {@inheritDoc}
     *
     * Evict moved or renamed items from the cache.
     */
    public void nodeModified(NodeState modified) {
        synchronized (cacheMonitor) {
            LRUEntry entry = (LRUEntry) idCache.get(modified.getNodeId());
            if (entry == null) {
                // Item not cached, ignore
                return;
            }

            PathMap.Element element = entry.getElement();

            Iterator iter = element.getChildren();
            while (iter.hasNext()) {
                PathMap.Element child = (PathMap.Element) iter.next();
                NodeState.ChildNodeEntry cne = modified.getChildNodeEntry(
                        child.getName(), child.getNormalizedIndex());
                if (cne == null) {
                    // Item does not exist, remove
                    child.remove();
                    remove(child);
                    return;
                }

                LRUEntry childEntry = (LRUEntry) child.get();
                if (childEntry != null && !cne.getId().equals(childEntry.getId())) {
                    // Different child item, remove
                    child.remove();
                    remove(child);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     */
    public void stateDestroyed(ItemState destroyed) {
        remove(destroyed.getId());
    }

    /**
     * {@inheritDoc}
     */
    public void stateDiscarded(ItemState discarded) {
        if (discarded.isTransient() && !discarded.hasOverlayedState()) {
            // a new node has been discarded -> remove from cache
            remove(discarded.getId());
        } else if (provider.hasItemState(discarded.getId())) {
            evict(discarded.getId());
        } else {
            remove(discarded.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void nodeAdded(NodeState state, QName name, int index, NodeId id) {
        // Optimization: ignore notifications for nodes that are not in the cache
        synchronized (cacheMonitor) {
            if (idCache.containsKey(state.getNodeId())) {
                try {
                    Path path = Path.create(getPath(state.getNodeId()), name, index, true);
                    insert(path, id);
                } catch (PathNotFoundException e) {
                    log.warn("Unable to get path of node " + state.getNodeId()
                            + ", event ignored.");
                } catch (MalformedPathException e) {
                    log.warn("Unable to create path of " + id, e);
                } catch (ItemNotFoundException e) {
                    log.warn("Unable to find item " + state.getNodeId(), e);
                } catch (RepositoryException e) {
                    log.warn("Unable to get path of " + state.getNodeId(), e);
                }
            }
        }
    }

    /**
     * {@inheritDoc}
     * <p/>
     * Generate subsequent add and remove notifications for every replacement. This method
     * currently assumes that the reordering is detectable by comparing the state's child
     * node entries to the overlayed state's child node entries. It is not able to handle
     * a transient reordering and will therefore evict its cached entry if such a situation
     * is detected.
     */
    public void nodesReplaced(NodeState state) {
        List entries = state.getReorderedChildNodeEntries();
        if (entries.size() == 0) {
            synchronized (cacheMonitor) {
                if (idCache.containsKey(state.getNodeId())) {
                    evict(state.getNodeId());
                }
            }
            return;
        }
        Iterator iter = entries.iterator();
        while (iter.hasNext()) {
            NodeState.ChildNodeEntry now = (NodeState.ChildNodeEntry) iter.next();
            NodeState.ChildNodeEntry old =
                    ((NodeState) state.getOverlayedState()).getChildNodeEntry(now.getId());

            if (old == null) {
                log.warn("Reordered child node not found in old list.");
                continue;
            }

            nodeAdded(state, now.getName(), now.getIndex(), now.getId());
            nodeRemoved(state, old.getName(), old.getIndex(), old.getId());
        }
    }

    /**
     * {@inheritDoc}
     */
    public void nodeRemoved(NodeState state, QName name, int index, NodeId id) {
        // Optimization: ignore notifications for nodes that are not in the cache
        synchronized (cacheMonitor) {
            if (idCache.containsKey(state.getNodeId())) {
                try {
                    Path path = Path.create(getPath(state.getNodeId()), name, index, true);
                    remove(path, id);
                } catch (PathNotFoundException e) {
                    log.warn("Unable to get path of node " + state.getNodeId()
                            + ", event ignored.");
                } catch (MalformedPathException e) {
                    log.warn("Unable to create path of " + id, e);
                } catch (ItemNotFoundException e) {
                    log.warn("Unable to get path of " + state.getNodeId(), e);
                } catch (RepositoryException e) {
                    log.warn("Unable to get path of " + state.getNodeId(), e);
                }
            }
        }
    }

    //------------------------------------------------------< private methods >

    /**
     * Return a cached element in the path map, given its id
     *
     * @param id node id
     * @return cached element, <code>null</code> if not found
     */
    private PathMap.Element get(ItemId id) {
        synchronized (cacheMonitor) {
            LRUEntry entry = (LRUEntry) idCache.get(id);
            if (entry != null) {
                entry.touch();
                return entry.getElement();
            }
            return null;
        }
    }

    /**
     * Return the nearest cached element in the path map, given a path.
     * The returned element is guaranteed to have an associated object that
     * is not <code>null</code>.
     *
     * @param path path
     * @return cached element, <code>null</code> if not found
     */
    private PathMap.Element map(Path path) {
        synchronized (cacheMonitor) {
            PathMap.Element element = pathCache.map(path, false);
            while (element != null) {
                LRUEntry entry = (LRUEntry) element.get();
                if (entry != null) {
                    entry.touch();
                    return element;
                }
                element = element.getParent();
            }
            return null;
        }
    }

    /**
     * Cache an item in the hierarchy given its id and path. Adds a listener
     * for this item state to get notified about changes.
     *
     * @param state node state
     * @param path  path to item
     */
    private void cache(NodeState state, Path path) {
        NodeId id = state.getNodeId();

        synchronized (cacheMonitor) {
            if (idCache.get(id) != null) {
                return;
            }
            if (idCache.size() >= upperLimit) {
                /**
                 * Remove least recently used item. Scans the LRU list from head to tail
                 * and removes the first item that has no children.
                 */
                LRUEntry entry = head;
                while (entry != null) {
                    PathMap.Element element = entry.getElement();
                    if (element.getChildrenCount() == 0) {
                        evict(entry, true);
                        return;
                    }
                    entry = entry.getNext();
                }
            }

            PathMap.Element element = pathCache.put(path);
            if (element.get() != null) {
                if (!id.equals(((LRUEntry) element.get()).getId())) {
                    log.warn("overwriting PathMap.Element");
                }
            }
            LRUEntry entry = new LRUEntry(id, element);
            element.set(entry);
            idCache.put(id, entry);
        }
    }

    /**
     * Return a flag indicating whether a certain element is cached.
     *
     * @param id item id
     * @return <code>true</code> if the item is already cached;
     *         <code>false</code> otherwise
     */
    private boolean isCached(ItemId id) {
        synchronized (cacheMonitor) {
            return idCache.get(id) != null;
        }
    }

    /**
     * Remove item from cache. Removes the associated <code>LRUEntry</code>
     * and the <code>PathMap.Element</code> with it. Indexes of same name
     * sibling elements are shifted!
     *
     * @param id item id
     */
    private void remove(ItemId id) {
        synchronized (cacheMonitor) {
            LRUEntry entry = (LRUEntry) idCache.get(id);
            if (entry != null) {
                remove(entry, true);
            }
        }
    }

    /**
     * Remove item from cache. Index of same name sibling items are shifted!
     *
     * @param entry               LRU entry
     * @param removeFromPathCache whether to remove from path cache
     */
    private void remove(LRUEntry entry, boolean removeFromPathCache) {
        // assert: synchronized (cacheMonitor)
        if (removeFromPathCache) {
            PathMap.Element element = entry.getElement();
            remove(element);
            element.remove();
        } else {
            idCache.remove(entry.getId());
            entry.remove();
        }
    }

    /**
     * Evict item from cache. Index of same name sibling items are <b>not</b>
     * shifted!
     *
     * @param entry               LRU entry
     * @param removeFromPathCache whether to remove from path cache
     */
    private void evict(LRUEntry entry, boolean removeFromPathCache) {
        // assert: synchronized (cacheMonitor)
        if (removeFromPathCache) {
            PathMap.Element element = entry.getElement();
            element.traverse(new PathMap.ElementVisitor() {
                public void elementVisited(PathMap.Element element) {
                    evict((LRUEntry) element.get(), false);
                }
            }, false);
            element.remove(false);
        } else {
            idCache.remove(entry.getId());
            entry.remove();
        }
    }

    /**
     * Evict item from cache. Evicts the associated <code>LRUEntry</code>
     * and the <code>PathMap.Element</code> with it. Indexes of same name
     * sibling elements are <b>not</b> shifted!
     *
     * @param id item id
     */
    private void evict(ItemId id) {
        synchronized (cacheMonitor) {
            LRUEntry entry = (LRUEntry) idCache.get(id);
            if (entry != null) {
                evict(entry, true);
            }
        }
    }

    /**
     * Remove path map element from cache. This will traverse all children
     * of this element and remove the objects associated with them.
     * Index of same name sibling items are shifted!
     *
     * @param element path map element
     */
    private void remove(PathMap.Element element) {
        // assert: synchronized (cacheMonitor)
        element.traverse(new PathMap.ElementVisitor() {
            public void elementVisited(PathMap.Element element) {
                remove((LRUEntry) element.get(), false);
            }
        }, false);
    }

    /**
     * Insert a node into the cache. This will automatically shift
     * all indexes of sibling nodes having index greater or equal.
     *
     * @param path child path
     * @param id   node id
     *
     * @throws PathNotFoundException if the path was not found
     */
    private void insert(Path path, ItemId id) throws PathNotFoundException {
        synchronized (cacheMonitor) {
            PathMap.Element element = null;

            LRUEntry entry = (LRUEntry) idCache.get(id);
            if (entry != null) {
                element = entry.getElement();
                element.remove();
            }

            PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
            if (parent != null) {
                parent.insert(path.getNameElement());
            }
            if (element != null) {
                pathCache.put(path, element);

                /* Remember this as a move */
                movedIds.add(id);
            }
        }
    }

    /**
     * Remove an item from the cache in order to shift the indexes
     * of items following this item.
     *
     * @param path child path
     * @param id   node id
     *
     * @throws PathNotFoundException if the path was not found
     */
    private void remove(Path path, ItemId id) throws PathNotFoundException {
        synchronized (cacheMonitor) {
            /* If we remembered this as a move, ignore this event */
            if (movedIds.remove(id)) {
                return;
            }
            PathMap.Element parent = pathCache.map(path.getAncestor(1), true);
            if (parent != null) {
                PathMap.Element element = parent.remove(path.getNameElement());
                if (element != null) {
                    remove(element);
                }
            }
        }
    }

    /**
     * Dump contents of path map and elements included to <code>PrintStream</code> given.
     *
     * @param ps print stream to dump to
     */
    public void dump(final PrintStream ps) {
        pathCache.traverse(new PathMap.ElementVisitor() {
            public void elementVisited(PathMap.Element element) {
                StringBuffer line = new StringBuffer();
                for (int i = 0; i < element.getDepth(); i++) {
                    line.append("--");
                }
                line.append(element.getName());
                int index = element.getIndex();
                if (index != 0 && index != 1) {
                    line.append('[');
                    line.append(index);
                    line.append(']');
                }
                line.append("  ");
                line.append(element.get());
                ps.println(line.toString());
            }
        }, true);
    }

    /**
     * Entry in the LRU list
     */
    private class LRUEntry {

        /**
         * Previous entry
         */
        private LRUEntry previous;

        /**
         * Next entry
         */
        private LRUEntry next;

        /**
         * Node id
         */
        private final NodeId id;

        /**
         * Element in path map
         */
        private final PathMap.Element element;

        /**
         * Create a new instance of this class
         *
         * @param id node id
         * @param element the path map element for this entry
         */
        public LRUEntry(NodeId id, PathMap.Element element) {
            this.id = id;
            this.element = element;

            append();
        }

        /**
         * Append entry to end of LRU list
         */
        public void append() {
            if (tail == null) {
                head = this;
                tail = this;
            } else {
                previous = tail;
                tail.next = this;
                tail = this;
            }
        }

        /**
         * Remove entry from LRU list
         */
        public void remove() {
            if (previous != null) {
                previous.next = next;
            }
            if (next != null) {
                next.previous = previous;
            }
            if (head == this) {
                head = next;
            }
            if (tail == this) {
                tail = previous;
            }
            previous = null;
            next = null;
        }

        /**
         * Touch entry. Removes it from its current position in the LRU list
         * and moves it to the end.
         */
        public void touch() {
            remove();
            append();
        }

        /**
         * Return previous LRU entry
         *
         * @return previous LRU entry
         */
        public LRUEntry getPrevious() {
            return previous;
        }

        /**
         * Return next LRU entry
         *
         * @return next LRU entry
         */
        public LRUEntry getNext() {
            return next;
        }

        /**
         * Return node ID
         *
         * @return node ID
         */
        public NodeId getId() {
            return id;
        }

        /**
         * Return element in path map
         *
         * @return element in path map
         */
        public PathMap.Element getElement() {
            return element;
        }

        /**
         * {@inheritDoc}
         */
        public String toString() {
            return id.toString();
        }
    }
}
TOP

Related Classes of org.apache.jackrabbit.core.CachingHierarchyManager$LRUEntry

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.