org.apache.jackrabbit.core.query.lucene.CachingIndexReader.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.jackrabbit.core.query.lucene.CachingIndexReader.java

Source

/*
 * 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.lucene.document.Document;
import org.apache.lucene.index.FilterIndexReader;
import org.apache.lucene.index.IndexReader;
import org.apache.lucene.index.Term;
import org.apache.lucene.index.TermDocs;
import org.apache.lucene.index.TermEnum;
import org.apache.jackrabbit.uuid.UUID;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.BitSet;
import java.util.Map;
import java.util.HashMap;
import java.util.Iterator;
import java.text.NumberFormat;

/**
 * Implements an <code>IndexReader</code> that maintains caches to resolve
 * {@link #getParent(int, BitSet)} calls efficiently.
 * <p/>
 */
class CachingIndexReader extends FilterIndexReader {

    /**
     * The logger instance for this class.
     */
    private static final Logger log = LoggerFactory.getLogger(CachingIndexReader.class);

    /**
     * The current value of the global creation tick counter.
     */
    private static long currentTick;

    /**
     * Cache of nodes parent relation. If an entry in the array is not null,
     * that means the node with the document number = array-index has the node
     * with <code>DocId</code> as parent.
     */
    private final DocId[] parents;

    /**
     * Tick when this index reader was created.
     */
    private final long creationTick = getNextCreationTick();

    /**
     * Document number cache if available. May be <code>null</code>.
     */
    private final DocNumberCache cache;

    /**
     * Creates a new <code>CachingIndexReader</code> based on
     * <code>delegatee</code>
     *
     * @param delegatee the base <code>IndexReader</code>.
     * @param cache     a document number cache, or <code>null</code> if not
     *                  available to this reader.
     * @throws IOException if an error occurs while reading from the index.
     */
    CachingIndexReader(IndexReader delegatee, DocNumberCache cache) throws IOException {
        super(delegatee);
        this.cache = cache;
        parents = new DocId[delegatee.maxDoc()];
        initializeParents(delegatee);
    }

    /**
     * Returns the <code>DocId</code> of the parent of <code>n</code> or
     * {@link DocId#NULL} if <code>n</code> does not have a parent
     * (<code>n</code> is the root node).
     *
     * @param n the document number.
     * @param deleted the documents that should be regarded as deleted.
     * @return the <code>DocId</code> of <code>n</code>'s parent.
     * @throws IOException if an error occurs while reading from the index.
     */
    DocId getParent(int n, BitSet deleted) throws IOException {
        DocId parent;
        boolean existing = false;
        parent = parents[n];

        if (parent != null) {
            existing = true;

            // check if valid and reset if necessary
            if (!parent.isValid(deleted)) {
                if (log.isDebugEnabled()) {
                    log.debug(parent + " not valid anymore.");
                }
                parent = null;
            }
        }

        if (parent == null) {
            Document doc = document(n, FieldSelectors.UUID_AND_PARENT);
            String parentUUID = doc.get(FieldNames.PARENT);
            if (parentUUID == null || parentUUID.length() == 0) {
                parent = DocId.NULL;
            } else {
                // only create a DocId from document number if there is no
                // existing DocId
                if (!existing) {
                    Term id = new Term(FieldNames.UUID, parentUUID);
                    TermDocs docs = termDocs(id);
                    try {
                        while (docs.next()) {
                            if (!deleted.get(docs.doc())) {
                                parent = DocId.create(docs.doc());
                                break;
                            }
                        }
                    } finally {
                        docs.close();
                    }
                }

                // if still null, then parent is not in this index, or existing
                // DocId was invalid. thus, only allowed to create DocId from uuid
                if (parent == null) {
                    parent = DocId.create(parentUUID);
                }
            }

            // finally put to cache
            parents[n] = parent;
        }
        return parent;
    }

    /**
     * Returns the tick value when this reader was created.
     *
     * @return the creation tick for this reader.
     */
    public long getCreationTick() {
        return creationTick;
    }

    //--------------------< FilterIndexReader overwrites >----------------------

    /**
     * If the field of <code>term</code> is {@link FieldNames#UUID} this
     * <code>CachingIndexReader</code> returns a <code>TermDocs</code> instance
     * with a cached document id. If <code>term</code> has any other field
     * the call is delegated to the base <code>IndexReader</code>.<br/>
     * If <code>term</code> is for a {@link FieldNames#UUID} field and this
     * <code>CachingIndexReader</code> does not have such a document,
     * {@link #EMPTY} is returned.
     *
     * @param term the term to start the <code>TermDocs</code> enumeration.
     * @return a TermDocs instance.
     * @throws IOException if an error occurs while reading from the index.
     */
    public TermDocs termDocs(Term term) throws IOException {
        if (term.field() == FieldNames.UUID) {
            // check cache if we have one
            if (cache != null) {
                DocNumberCache.Entry e = cache.get(term.text());
                if (e != null) {
                    // check if valid
                    // the cache may contain entries from a different reader
                    // with the same uuid. that happens when a node is updated
                    // and is reindexed. the node 'travels' from an older index
                    // to a newer one. the cache will still contain a cache
                    // entry from the old until it is overwritten by the
                    // newer index.
                    if (e.creationTick == creationTick && !isDeleted(e.doc)) {
                        return new SingleTermDocs(e.doc);
                    }
                }

                // not in cache or invalid
                TermDocs docs = in.termDocs(term);
                try {
                    if (docs.next()) {
                        // put to cache
                        cache.put(term.text(), this, docs.doc());
                        // and return
                        return new SingleTermDocs(docs.doc());
                    } else {
                        return EMPTY;
                    }
                } finally {
                    docs.close();
                }
            }
        }
        return super.termDocs(term);
    }

    //----------------------< internal >----------------------------------------

    /**
     * Returns the next creation tick value.
     *
     * @return the next creation tick value.
     */
    private static long getNextCreationTick() {
        synchronized (CachingIndexReader.class) {
            return currentTick++;
        }
    }

    /**
     * Initializes the {@link #parents} <code>DocId</code> array.
     *
     * @param reader the underlying index reader.
     * @throws IOException if an error occurs while reading from the index.
     */
    private void initializeParents(IndexReader reader) throws IOException {
        long time = System.currentTimeMillis();
        Map docs = new HashMap();
        for (int i = 0; i < reader.maxDoc(); i++) {
            if (!reader.isDeleted(i)) {
                Document doc = reader.document(i, FieldSelectors.UUID_AND_PARENT);
                UUID uuid = UUID.fromString(doc.get(FieldNames.UUID));
                UUID parent = null;
                try {
                    parent = UUID.fromString(doc.get(FieldNames.PARENT));
                } catch (IllegalArgumentException e) {
                    // root node does not have a parent
                }
                NodeInfo info = new NodeInfo(i, uuid, parent);
                docs.put(uuid, info);
            }
        }
        double foreignParents = 0;
        Iterator it = docs.values().iterator();
        while (it.hasNext()) {
            NodeInfo info = (NodeInfo) it.next();
            NodeInfo parent = (NodeInfo) docs.get(info.parent);
            if (parent != null) {
                parents[info.docId] = DocId.create(parent.docId);
            } else if (info.parent != null) {
                foreignParents++;
                parents[info.docId] = DocId.create(info.parent);
            } else {
                // no parent -> root node
                parents[info.docId] = DocId.NULL;
            }
        }
        if (log.isDebugEnabled()) {
            NumberFormat nf = NumberFormat.getPercentInstance();
            nf.setMaximumFractionDigits(1);
            time = System.currentTimeMillis() - time;
            if (parents.length > 0) {
                foreignParents /= parents.length;
            }
            log.debug("initialized {} DocIds in {} ms, {} foreign parents",
                    new Object[] { new Integer(parents.length), new Long(time), nf.format(foreignParents) });
        }
    }

    private static class NodeInfo {

        final int docId;

        final UUID uuid;

        final UUID parent;

        public NodeInfo(int docId, UUID uuid, UUID parent) {
            this.docId = docId;
            this.uuid = uuid;
            this.parent = parent;
        }
    }

    /**
     * Implements an empty TermDocs.
     */
    static final TermDocs EMPTY = new TermDocs() {

        public void seek(Term term) {
        }

        public void seek(TermEnum termEnum) {
        }

        public int doc() {
            return -1;
        }

        public int freq() {
            return -1;
        }

        public boolean next() {
            return false;
        }

        public int read(int[] docs, int[] freqs) {
            return 0;
        }

        public boolean skipTo(int target) {
            return false;
        }

        public void close() {
        }
    };
}