You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@jackrabbit.apache.org by mr...@apache.org on 2005/08/22 16:20:20 UTC

svn commit: r234492 - /incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/

Author: mreutegg
Date: Mon Aug 22 07:20:02 2005
New Revision: 234492

URL: http://svn.apache.org/viewcvs?rev=234492&view=rev
Log:
JCR-190: Caching in QueryHandler does not scale well

Added:
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java   (with props)
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java   (with props)
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java   (with props)
Modified:
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SharedIndexReader.java
    incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/VolatileIndex.java

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/AbstractIndex.java Mon Aug 22 07:20:02 2005
@@ -75,19 +75,30 @@
     /** mergeFactor config parameter */
     private int mergeFactor = 10;
 
+    /**
+     * The document number cache if this index may use one.
+     */
+    private DocNumberCache cache;
+
     /** The shared IndexReader for all read-only IndexReaders */
     private SharedIndexReader sharedReader;
 
     /**
      * Constructs an index with an <code>analyzer</code> and a
      * <code>directory</code>.
-     * @param analyzer the analyzer for text tokenizing.
+     *
+     * @param analyzer  the analyzer for text tokenizing.
      * @param directory the underlying directory.
+     * @param cache     the document number cache if this index should use one;
+     *                  otherwise <code>cache</code> is <code>null</code>.
      * @throws IOException if the index cannot be initialized.
      */
-    AbstractIndex(Analyzer analyzer, Directory directory) throws IOException {
+    AbstractIndex(Analyzer analyzer,
+                  Directory directory,
+                  DocNumberCache cache) throws IOException {
         this.analyzer = analyzer;
         this.directory = directory;
+        this.cache = cache;
 
         if (!IndexReader.indexExists(directory)) {
             indexWriter = new IndexWriter(directory, analyzer, true);
@@ -173,7 +184,8 @@
         }
         if (sharedReader == null) {
             // create new shared reader
-            sharedReader = new SharedIndexReader(new CachingIndexReader(IndexReader.open(getDirectory())));
+            CachingIndexReader cr = new CachingIndexReader(IndexReader.open(getDirectory()), cache);
+            sharedReader = new SharedIndexReader(cr);
         }
         return new ReadOnlyIndexReader(sharedReader, deleted);
     }

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingIndexReader.java Mon Aug 22 07:20:02 2005
@@ -25,23 +25,12 @@
 import org.apache.lucene.index.TermEnum;
 
 import java.io.IOException;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
+import java.util.BitSet;
 
 /**
  * Implements an <code>IndexReader</code> that maintains caches to resolve
- * {@link IndexReader#termDocs(Term)} calls efficiently.
+ * {@link #getParent(int, BitSet)} calls efficiently.
  * <p/>
- * The caches are:
- * <ul>
- * <li>idCache: maps UUID to document number</li>
- * <li>documentCache: maps document number to {@link Document} instance</li>
- * <li>parentCache: maps parentUUID to List of document numbers</li>
- * </ul>
  */
 class CachingIndexReader extends FilterIndexReader {
 
@@ -51,181 +40,180 @@
     private static final Logger log = Logger.getLogger(CachingIndexReader.class);
 
     /**
-     * The document idCache. Maps UUIDs to document number.
+     * The current value of the global creation tick counter.
      */
-    private Map idCache;
+    private static long currentTick;
 
     /**
-     * The document cache. Maps document number to Document instance.
+     * 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 Map documentCache;
+    private final DocId[] parents;
 
     /**
-     * The parent id cache. Maps parent UUID to List of document numbers.
+     * Tick when this index reader was created.
      */
-    private Map parentCache;
+    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.
      */
-    CachingIndexReader(IndexReader delegatee) {
+    CachingIndexReader(IndexReader delegatee, DocNumberCache cache) {
         super(delegatee);
+        this.cache = cache;
+        parents = new DocId[delegatee.maxDoc()];
     }
 
     /**
-     * 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.
+     * 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 term the term to start the <code>TermDocs</code> enumeration.
-     * @return a TermDocs instance.
+     * @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.
      */
-    public TermDocs termDocs(Term term) throws IOException {
-        if (term.field() == FieldNames.UUID) {
-            synchronized (this) {
-                cacheInit();
-                Integer docNo = (Integer) idCache.get(term.text());
-                if (docNo == null) {
-                    return EMPTY;
-                } else {
-                    return new CachingTermDocs(docNo);
+    DocId getParent(int n, BitSet deleted) throws IOException {
+        DocId parent;
+        boolean existing = false;
+        synchronized (parents) {
+            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;
             }
-        } else if (term.field() == FieldNames.PARENT) {
-            synchronized (this) {
-                cacheInit();
-                List idList = (List) parentCache.get(term.text());
-                if (idList == null) {
-                    return EMPTY;
-                } else {
-                    return new CachingTermDocs(idList.iterator());
+        }
+
+        if (parent == null) {
+            Document doc = document(n);
+            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);
                 }
             }
-        } else {
-            return super.termDocs(term);
-        }
-    }
 
-    /**
-     * Returns the stored fields of the <code>n</code><sup>th</sup>
-     * <code>Document</code> in this index. This implementation returns cached
-     * versions of <code>Document</code> instance. Thus, the returned document
-     * must not be modified!
-     *
-     * @param n the document number.
-     * @return the <code>n</code><sup>th</sup> <code>Document</code> in this
-     *         index
-     * @throws IOException              if an error occurs while reading from
-     *                                  the index.
-     * @throws IllegalArgumentException if the document with number
-     *                                  <code>n</code> is deleted.
-     */
-    public Document document(int n) throws IOException, IllegalArgumentException {
-        if (isDeleted(n)) {
-            throw new IllegalArgumentException("attempt to access a deleted document");
-        }
-        synchronized (this) {
-            cacheInit();
-            return (Document) documentCache.get(new Integer(n));
+            // finally put to cache
+            synchronized (parents) {
+                parents[n] = parent;
+            }
         }
+        return parent;
     }
 
     /**
-     * Commits pending changes to disc.
-     * @throws IOException if an error occurs while writing changes.
+     * Returns the tick value when this reader was created.
+     *
+     * @return the creation tick for this reader.
      */
-    public void commitDeleted() throws IOException {
-        commit();
+    public long getCreationTick() {
+        return creationTick;
     }
 
+    //--------------------< FilterIndexReader overwrites >----------------------
+
     /**
-     * Provides an efficient lookup of document frequency for terms with field
-     * {@link FieldNames#UUID} and {@link FieldNames#PARENT}. All other calles
-     * are handled by the base class.
+     * 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 t the term to look up the document frequency.
-     * @return the document frequency of term <code>t</code>.
+     * @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 int docFreq(Term t) throws IOException {
-        synchronized (this) {
-            cacheInit();
-            if (t.field() == FieldNames.UUID) {
-                return idCache.containsKey(t.text()) ? 1 : 0;
-            } else if (t.field() == FieldNames.PARENT) {
-                List children = (List) parentCache.get(t.text());
-                return children == null ? 0 : children.size();
-            }
-        }
-        return super.docFreq(t);
-    }
+    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.reader == this && !isDeleted(e.doc)) {
+                        return new SingleTermDocs(e.doc);
+                    }
+                }
 
-    /**
-     * Removes the <code>TermEnum</code> from the idCache and calls the base
-     * <code>IndexReader</code>.
-     * @param n the number of the document to delete.
-     * @throws IOException if an error occurs while deleting the document.
-     */
-    protected synchronized void doDelete(int n) throws IOException {
-        if (idCache != null) {
-            Document d = (Document) documentCache.remove(new Integer(n));
-            if (d != null) {
-                idCache.remove(d.get(FieldNames.UUID));
-                String parentUUID = d.get(FieldNames.PARENT);
-                List parents = (List) parentCache.get(parentUUID);
-                if (parents.size() == 1) {
-                    parentCache.remove(parentUUID);
-                } else {
-                    // replace existing list, other threads might use iterator
-                    // on existing list
-                    List repl = new ArrayList(parents);
-                    repl.remove(new Integer(n));
-                    parentCache.put(parentUUID, repl);
+                // 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();
                 }
             }
         }
-        super.doDelete(n);
+        return super.termDocs(term);
     }
 
+
+    //----------------------< internal >----------------------------------------
+
     /**
-     * Initially fills the caches: idCache, documentCache, parentCache.
-     * @throws IOException if an error occurs while reading from the index.
+     * Returns the next creation tick value.
+     *
+     * @return the next creation tick value.
      */
-    private void cacheInit() throws IOException {
-        if (idCache == null) {
-            long time = System.currentTimeMillis();
-            Map ids = new HashMap(in.numDocs());
-            Map documents = new HashMap(in.numDocs());
-            Map parents = new HashMap(in.numDocs());
-            for (int i = 0; i < in.maxDoc(); i++) {
-                if (!in.isDeleted(i)) {
-                    Document d = in.document(i);
-                    Integer docId = new Integer(i);
-                    if (ids.put(d.get(FieldNames.UUID), docId) != null) {
-                        log.warn("Duplicate index entry for node: " + d.get(FieldNames.UUID));
-                    }
-                    documents.put(docId, d);
-                    String parentUUID = d.get(FieldNames.PARENT);
-                    List docIds = (List) parents.get(parentUUID);
-                    if (docIds == null) {
-                        docIds = new ArrayList();
-                        parents.put(parentUUID, docIds);
-                    }
-                    docIds.add(docId);
-                }
-            }
-            idCache = ids;
-            documentCache = documents;
-            parentCache = parents;
-            time = System.currentTimeMillis() - time;
-            log.debug("IndexReader cache populated in: " + time + " ms.");
+    private static long getNextCreationTick() {
+        synchronized (CachingIndexReader.class) {
+            return currentTick++;
         }
     }
 
@@ -263,99 +251,4 @@
         public void close() {
         }
     };
-
-    /**
-     * Implements a <code>TermDocs</code> that takes a list of document
-     * ids.
-     */
-    private static final class CachingTermDocs implements TermDocs {
-
-        /**
-         * The current document number.
-         */
-        private int current = -1;
-
-        /**
-         * Iterator over document numbers as <code>Integer</code> values.
-         */
-        private final Iterator docIds;
-
-        /**
-         * Creates a new <code>CachingTermDocs</code> instance with a single
-         * document id.
-         * @param docId the single document id.
-         */
-        CachingTermDocs(Integer docId) {
-            this(Arrays.asList(new Integer[]{docId}).iterator());
-        }
-
-        /**
-         * Creates a new <code>CachingTermDocs</code> instance that iterates
-         * over the <code>docIds</code>.
-         * @param docIds the actual document numbers / ids.
-         */
-        CachingTermDocs(Iterator docIds) {
-            this.docIds = docIds;
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(Term term) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(TermEnum termEnum) {
-            throw new UnsupportedOperationException();
-        }
-
-
-        /**
-         * {@inheritDoc}
-         */
-        public int doc() {
-            return current;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public int freq() {
-            return 1;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public boolean next() {
-            boolean next = docIds.hasNext();
-            if (next) {
-                current = ((Integer) docIds.next()).intValue();
-            }
-            return next;
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public int read(int[] docs, int[] freqs) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public boolean skipTo(int target) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public void close() {
-        }
-    }
 }

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java Mon Aug 22 07:20:02 2005
@@ -16,24 +16,34 @@
  */
 package org.apache.jackrabbit.core.query.lucene;
 
-import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiReader;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.TermDocs;
-import org.apache.lucene.index.TermEnum;
 
 import java.io.IOException;
+import java.util.Map;
+import java.util.IdentityHashMap;
 
 /**
  * Extends a <code>MultiReader</code> with support for cached <code>TermDocs</code>
  * on {@link FieldNames#UUID} field.
  */
-class CachingMultiReader extends MultiReader {
+final class CachingMultiReader extends MultiReader {
 
     /**
      * The sub readers.
      */
-    private IndexReader[] subReaders;
+    private ReadOnlyIndexReader[] subReaders;
+
+    /**
+     * Map of OffsetReaders, identified by caching reader they are based on.
+     */
+    private final Map readersByBase = new IdentityHashMap();
+
+    /**
+     * Document number cache if available. May be <code>null</code>.
+     */
+    private final DocNumberCache cache;
 
     /**
      * Doc number starts for each sub reader
@@ -48,57 +58,75 @@
 
     /**
      * Creates a new <code>CachingMultiReader</code> based on sub readers.
-     * <p/>
-     * This <code>CachingMultiReader</code> poses type requirements on the
-     * <code>subReaders</code>: all but one sub readers must be a
-     * {@link ReadOnlyIndexReader}. The single allowed sub reader not of type
-     * {@link ReadOnlyIndexReader} must be the last reader in
-     * <code>subReaders</code>! Otherwise this constructor will throw an
-     * {@link IllegalArgumentException}.
      *
      * @param subReaders the sub readers.
+     * @param cache the document number cache.
      * @throws IOException if an error occurs while reading from the indexes.
-     * @exception IllegalArgumentException if <code>subReaders</code> does
-     * not comply to the above type requirements.
      */
-    public CachingMultiReader(IndexReader[] subReaders)
-            throws IOException, IllegalArgumentException {
+    public CachingMultiReader(ReadOnlyIndexReader[] subReaders,
+                              DocNumberCache cache)
+            throws IOException {
         super(subReaders);
-        // check readers, all but last must be a ReadOnlyIndexReader
-        for (int i = 0; i < subReaders.length - 1; i++) {
-            if (!(subReaders[i] instanceof ReadOnlyIndexReader)) {
-                throw new IllegalArgumentException("subReader " + i + " must be of type ReadOnlyIndexReader");
-            }
-        }
+        this.cache = cache;
         this.subReaders = subReaders;
         starts = new int[subReaders.length + 1];
         int maxDoc = 0;
         for (int i = 0; i < subReaders.length; i++) {
             starts[i] = maxDoc;
             maxDoc += subReaders[i].maxDoc();
+            OffsetReader offsetReader = new OffsetReader(subReaders[i], starts[i]);
+            readersByBase.put(subReaders[i].getBase().getBase(), offsetReader);
         }
         starts[subReaders.length] = maxDoc;
     }
 
     /**
+     * Returns the document number of the parent of <code>n</code> or
+     * <code>-1</code> if <code>n</code> does not have a parent (<code>n</code>
+     * is the root node).
+     *
+     * @param n the document number.
+     * @return the document number of <code>n</code>'s parent.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    final public int getParent(int n) throws IOException {
+        int i = readerIndex(n);
+        DocId id = subReaders[i].getParent(n - starts[i]);
+        id = id.applyOffset(starts[i]);
+        return id.getDocumentNumber(this);
+    }
+
+    /**
      * {@inheritDoc}
      */
     public TermDocs termDocs(Term term) throws IOException {
         if (term.field() == FieldNames.UUID) {
-            for (int i = 0; i < subReaders.length; i++) {
-                TermDocs docs = subReaders[i].termDocs(term);
-                if (docs != CachingIndexReader.EMPTY) {
-                    // apply offset
-                    return new OffsetTermDocs(docs, starts[i]);
+            // check cache
+            DocNumberCache.Entry e = cache.get(term.text());
+            if (e != null) {
+                // check if valid:
+                // 1) reader must be in the set of readers
+                // 2) doc must not be deleted
+                OffsetReader offsetReader = (OffsetReader) readersByBase.get(e.reader);
+                if (offsetReader != null && !offsetReader.reader.isDeleted(e.doc)) {
+                    return new SingleTermDocs(e.doc + offsetReader.offset);
                 }
             }
-        } else if (term.field() == FieldNames.PARENT) {
-            TermDocs[] termDocs = new TermDocs[subReaders.length];
+
+            // if we get here, entry is either invalid or did not exist
+            // search through readers
             for (int i = 0; i < subReaders.length; i++) {
-                termDocs[i] = subReaders[i].termDocs(term);
+                TermDocs docs = subReaders[i].termDocs(term);
+                try {
+                    if (docs.next()) {
+                        return new SingleTermDocs(docs.doc() + starts[i]);
+                    }
+                } finally {
+                    docs.close();
+                }
             }
-            return new MultiTermDocs(termDocs, starts);
         }
+
         return super.termDocs(term);
     }
 
@@ -121,203 +149,62 @@
         }
     }
 
+    //------------------------< internal >--------------------------------------
+
     /**
-     * Partial <code>TermDocs</code> implementation that applies an offset
-     * to a base <code>TermDocs</code> instance.
+     * Returns the reader index for document <code>n</code>.
+     * Implementation copied from lucene MultiReader class.
+     *
+     * @param n document number.
+     * @return the reader index.
      */
-    private static final class OffsetTermDocs implements TermDocs {
-
-        /**
-         * The base <code>TermDocs</code> instance.
-         */
-        private final TermDocs base;
-
-        /**
-         * The offset to apply
-         */
-        private final int offset;
-
-        /**
-         * Creates a new <code>OffsetTermDocs</code> instance.
-         * @param base the base <code>TermDocs</code>.
-         * @param offset the offset to apply.
-         */
-        OffsetTermDocs(TermDocs base, int offset) {
-            this.base = base;
-            this.offset = offset;
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(Term term) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(TermEnum termEnum) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public int doc() {
-            return base.doc() + offset;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public int freq() {
-            return base.freq();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public boolean next() throws IOException {
-            return base.next();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public int read(int[] docs, int[] freqs) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public boolean skipTo(int target) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public void close() throws IOException {
-            base.close();
+    final private int readerIndex(int n) {
+        int lo = 0;                                      // search starts array
+        int hi = subReaders.length - 1;                  // for first element less
+
+        while (hi >= lo) {
+            int mid = (lo + hi) >> 1;
+            int midValue = starts[mid];
+            if (n < midValue) {
+                hi = mid - 1;
+            } else if (n > midValue) {
+                lo = mid + 1;
+            } else {                                      // found a match
+                while (mid + 1 < subReaders.length && starts[mid + 1] == midValue) {
+                    mid++;                                  // scan to last match
+                }
+                return mid;
+            }
         }
+        return hi;
     }
 
+    //-----------------------< OffsetTermDocs >---------------------------------
+
     /**
-     * Implements a <code>TermDocs</code> which spans multiple other
-     * <code>TermDocs</code>.
+     * Simple helper struct that associates an offset with an IndexReader.
      */
-    private static final class MultiTermDocs implements TermDocs {
-
-        /**
-         * The actual <code>TermDocs</code>.
-         */
-        private final TermDocs[] termDocs;
-
-        /**
-         * The document number offsets for each <code>TermDocs</code>.
-         */
-        private final int[] starts;
-
-        /**
-         * The current <code>TermDocs</code> instance. If <code>null</code>
-         * there are no more documents.
-         */
-        private TermDocs current;
-
-        /**
-         * The current index into {@link #termDocs} and {@link #starts}.
-         */
-        private int idx = 0;
-
-        /**
-         * Creates a new <code>MultiTermDocs</code> instance.
-         * @param termDocs the actual <code>TermDocs</code>.
-         * @param starts the document number offsets for each
-         *  <code>TermDocs</code>
-         */
-        MultiTermDocs(TermDocs[] termDocs, int[] starts) {
-            this.termDocs = termDocs;
-            this.starts = starts;
-            current = termDocs[idx];
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(Term term) {
-            throw new UnsupportedOperationException();
-        }
-
-        /**
-         * @throws UnsupportedOperationException always
-         */
-        public void seek(TermEnum termEnum) {
-            throw new UnsupportedOperationException();
-        }
+    private static final class OffsetReader {
 
         /**
-         * {@inheritDoc}
+         * The index reader.
          */
-        public int doc() {
-            return starts[idx] + current.doc();
-        }
+        final ReadOnlyIndexReader reader;
 
         /**
-         * {@inheritDoc}
+         * The reader offset in this multi reader instance.
          */
-        public int freq() {
-            return current.freq();
-        }
+        final int offset;
 
         /**
-         * {@inheritDoc}
+         * Creates a new <code>OffsetReader</code>.
+         *
+         * @param reader the index reader.
+         * @param offset the reader offset in a multi reader.
          */
-        public boolean next() throws IOException {
-            while (current != null && !current.next()) {
-                if (++idx >= termDocs.length) {
-                    // no more TermDocs
-                    current = null;
-                } else {
-                    // move to next TermDocs
-                    current = termDocs[idx];
-                }
-            }
-            return current != null;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public int read(int[] docs, int[] freqs) throws IOException {
-            int count = 0;
-            for (int i = 0; i < docs.length && next(); i++, count++) {
-                docs[i] = doc();
-                freqs[i] = freq();
-            }
-            return count;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public boolean skipTo(int target) throws IOException {
-            do {
-                if (!next()) {
-                    return false;
-                }
-            } while (target > doc());
-            return true;
-        }
-
-        /**
-         * {@inheritDoc}
-         */
-        public void close() throws IOException {
-            for (int i = 0; i < termDocs.length; i++) {
-                termDocs[i].close();
-            }
+        OffsetReader(ReadOnlyIndexReader reader, int offset) {
+            this.reader = reader;
+            this.offset = offset;
         }
     }
 }

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java Mon Aug 22 07:20:02 2005
@@ -17,8 +17,6 @@
 package org.apache.jackrabbit.core.query.lucene;
 
 import org.apache.lucene.index.IndexReader;
-import org.apache.lucene.index.Term;
-import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.search.Explanation;
 import org.apache.lucene.search.HitCollector;
 import org.apache.lucene.search.Query;
@@ -26,12 +24,9 @@
 import org.apache.lucene.search.Searcher;
 import org.apache.lucene.search.Similarity;
 import org.apache.lucene.search.Weight;
-import org.apache.lucene.document.Document;
 
 import java.io.IOException;
 import java.util.BitSet;
-import java.util.HashSet;
-import java.util.Set;
 
 /**
  * Implements a lucene <code>Query</code> which filters a sub query by checking
@@ -175,7 +170,8 @@
         public Scorer scorer(IndexReader reader) throws IOException {
             contextScorer = contextQuery.weight(searcher).scorer(reader);
             subScorer = subQuery.weight(searcher).scorer(reader);
-            return new DescendantSelfAxisScorer(searcher.getSimilarity(), reader);
+            CachingMultiReader index = (CachingMultiReader) reader;
+            return new DescendantSelfAxisScorer(searcher.getSimilarity(), index);
         }
 
         /**
@@ -196,12 +192,12 @@
         /**
          * An <code>IndexReader</code> to access the index.
          */
-        private final IndexReader reader;
+        private final CachingMultiReader reader;
 
         /**
          * BitSet storing the id's of selected documents
          */
-        private final BitSet hits;
+        private final BitSet contextHits;
 
         /**
          * BitSet storing the id's of selected documents from the sub query
@@ -209,14 +205,14 @@
         private final BitSet subHits;
 
         /**
-         * List of UUIDs of selected nodes by the context query
+         * The next document id to return
          */
-        private Set contextUUIDs = null;
+        private int nextDoc = -1;
 
         /**
-         * The next document id to return
+         * Set <code>true</code> once the sub contextHits have been calculated.
          */
-        private int nextDoc = -1;
+        private boolean subHitsCalculated = false;
 
         /**
          * Creates a new <code>DescendantSelfAxisScorer</code>.
@@ -224,10 +220,11 @@
          * @param similarity the <code>Similarity</code> instance to use.
          * @param reader     for index access.
          */
-        protected DescendantSelfAxisScorer(Similarity similarity, IndexReader reader) {
+        protected DescendantSelfAxisScorer(Similarity similarity, CachingMultiReader reader) {
             super(similarity);
             this.reader = reader;
-            this.hits = new BitSet(reader.maxDoc());
+            // todo reuse BitSets?
+            this.contextHits = new BitSet(reader.maxDoc());
             this.subHits = new BitSet(reader.maxDoc());
         }
 
@@ -242,38 +239,25 @@
 
                 // check self if necessary
                 if (includeSelf) {
-                    String uuid = reader.document(nextDoc).get(FieldNames.UUID);
-                    if (contextUUIDs.contains(uuid)) {
+                    if (contextHits.get(nextDoc)) {
                         return true;
                     }
                 }
 
                 // check if nextDoc is a descendant of one of the context nodes
-                Document d = reader.document(nextDoc);
-                String parentUUID = d.get(FieldNames.PARENT);
-                while (parentUUID != null && !contextUUIDs.contains(parentUUID)) {
+                int parentDoc = reader.getParent(nextDoc);
+                while (parentDoc != -1 && !contextHits.get(parentDoc)) {
                     // traverse
-                    TermDocs ancestor = reader.termDocs(new Term(FieldNames.UUID, parentUUID));
-                    try {
-                        if (ancestor.next()) {
-                            d = reader.document(ancestor.doc());
-                            parentUUID = d.get(FieldNames.PARENT);
-                            if (parentUUID.length() == 0) {
-                                parentUUID = null;
-                            }
-                        } else {
-                            parentUUID = null;
-                        }
-                    } finally {
-                        ancestor.close();
-                    }
+                    parentDoc = reader.getParent(parentDoc);
                 }
-                if (parentUUID != null) {
-                    // since current doc is a descendant of one of the context
-                    // docs we can promote uuid of doc to the context uuids
-                    contextUUIDs.add(d.get(FieldNames.UUID));
+
+                if (parentDoc != -1) {
+                    // since current parentDoc is a descendant of one of the context
+                    // docs we can promote parentDoc to the context hits
+                    contextHits.set(parentDoc);
                     return true;
                 }
+
                 // try next
                 nextDoc = subHits.nextSetBit(nextDoc + 1);
             }
@@ -303,26 +287,25 @@
         }
 
         private void calculateSubHits() throws IOException {
-            if (contextUUIDs == null) {
-                contextUUIDs = new HashSet();
+            if (!subHitsCalculated) {
+
                 contextScorer.score(new HitCollector() {
                     public void collect(int doc, float score) {
-                        // @todo maintain cache of doc id hierarchy
-                        hits.set(doc);
+                        contextHits.set(doc);
                     }
                 }); // find all
-                for (int i = hits.nextSetBit(0); i >= 0; i = hits.nextSetBit(i + 1)) {
-                    contextUUIDs.add(reader.document(i).get(FieldNames.UUID));
-                }
 
-                // reuse for final hits
-                hits.clear();
+                if (contextHits.isEmpty()) {
+                    // no need to execute sub scorer, context is empty
+                } else {
+                    subScorer.score(new HitCollector() {
+                        public void collect(int doc, float score) {
+                            subHits.set(doc);
+                        }
+                    });
+                }
 
-                subScorer.score(new HitCollector() {
-                    public void collect(int doc, float score) {
-                        subHits.set(doc);
-                    }
-                });
+                subHitsCalculated = true;
             }
         }
 

Added: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java?rev=234492&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java (added)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java Mon Aug 22 07:20:02 2005
@@ -0,0 +1,234 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
+
+import java.io.IOException;
+import java.util.BitSet;
+
+/**
+ * Implements a document id which can be based on a Node uuid or a lucene
+ * document number.
+ */
+abstract class DocId {
+
+    /**
+     * Indicates a null DocId. Will be returned if the root node is asked for
+     * its parent.
+     */
+    static final DocId NULL = new DocId() {
+
+        /**
+         * Always returns <code>-1</code>.
+         * @param reader the index reader.
+         * @return always <code>-1</code>.
+         */
+        final int getDocumentNumber(IndexReader reader) {
+            return -1;
+        }
+
+        /**
+         * Always returns <code>this</code>.
+         * @param offset the offset to apply.
+         * @return always <code>this</code>.
+         */
+        final DocId applyOffset(int offset) {
+            return this;
+        }
+
+        /**
+         * Always returns <code>true</code>.
+         * @param deleted the deleted documents.
+         * @return always <code>true</code>.
+         */
+        final boolean isValid(BitSet deleted) {
+            return true;
+        }
+    };
+
+    /**
+     * Returns the document number of this <code>DocId</code>. If this id is
+     * invalid <code>-1</code> is returned.
+     *
+     * @param reader the IndexReader to resolve this <code>DocId</code>.
+     * @return the document number of this <code>DocId</code> or <code>-1</code>
+     *         if it is invalid (e.g. does not exist).
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    abstract int getDocumentNumber(IndexReader reader) throws IOException;
+
+    /**
+     * Applies an offset to this <code>DocId</code>. The returned <code>DocId</code>
+     * may be the same as <code>this</code> if this <code>DocId</code> does
+     * not need to know about an offset.
+     *
+     * @param offset the offset to apply to.
+     * @return <code>DocId</code> with <code>offset</code> applied.
+     */
+    abstract DocId applyOffset(int offset);
+
+    /**
+     * Returns <code>true</code> if this <code>DocId</code> is valid against the
+     * set of <code>deleted</code> documents; otherwise <code>false</code>.
+     *
+     * @param deleted the deleted documents.
+     * @return <code>true</code> if this <code>DocId</code> is not delted;
+     *         otherwise <code>false</code>.
+     */
+    abstract boolean isValid(BitSet deleted);
+
+    /**
+     * Creates a <code>DocId</code> based on a document number.
+     *
+     * @param docNumber the document number.
+     * @return a <code>DocId</code> based on a document number.
+     */
+    static DocId create(int docNumber) {
+        return new PlainDocId(docNumber);
+    }
+
+    /**
+     * Creates a <code>DocId</code> based on a node UUID.
+     *
+     * @param uuid the node uuid.
+     * @return a <code>DocId</code> based on a node UUID.
+     */
+    static DocId create(String uuid) {
+        return new UUIDDocId(uuid);
+    }
+
+    //--------------------------< internal >------------------------------------
+
+    /**
+     * <code>DocId</code> based on a document number.
+     */
+    private static final class PlainDocId extends DocId {
+
+        /**
+         * The document number or <code>-1</code> if not set.
+         */
+        private final int docNumber;
+
+        /**
+         * Creates a <code>DocId</code> based on a document number.
+         *
+         * @param docNumber the lucene document number.
+         */
+        PlainDocId(int docNumber) {
+            this.docNumber = docNumber;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        final int getDocumentNumber(IndexReader reader) {
+            return docNumber;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        final DocId applyOffset(int offset) {
+            return new PlainDocId(docNumber + offset);
+        }
+
+        /**
+         * @inheritDoc
+         */
+        final boolean isValid(BitSet deleted) {
+            return !deleted.get(docNumber);
+        }
+
+        /**
+         * Returns a String representation for this <code>DocId</code>.
+         *
+         * @return a String representation for this <code>DocId</code>.
+         */
+        final public String toString() {
+            return "PlainDocId(" + docNumber + ")";
+        }
+    }
+
+    /**
+     * <code>DocId</code> based on a UUID.
+     */
+    private static final class UUIDDocId extends DocId {
+
+        /**
+         * The node uuid or <code>null</code> if not set.
+         */
+        private final String uuid;
+
+        /**
+         * Creates a <code>DocId</code> based on a Node uuid.
+         *
+         * @param uuid the Node uuid.
+         */
+        UUIDDocId(String uuid) {
+            this.uuid = uuid;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        final int getDocumentNumber(IndexReader reader) throws IOException {
+            Term id = new Term(FieldNames.UUID, uuid);
+            TermDocs docs = reader.termDocs(id);
+            int doc = -1;
+            try {
+                if (docs.next()) {
+                    doc = docs.doc();
+                }
+            } finally {
+                docs.close();
+            }
+            return doc;
+        }
+
+        /**
+         * This implementation will return <code>this</code>. Document number is
+         * not known until resolved in {@link #getDocumentNumber(IndexReader)}.
+         *
+         * @inheritDoc
+         */
+        final DocId applyOffset(int offset) {
+            return this;
+        }
+
+        /**
+         * Always returns <code>true</code>.
+         *
+         * @param deleted the deleted documents.
+         * @return always <code>true</code>.
+         */
+        final boolean isValid(BitSet deleted) {
+            return true;
+        }
+
+        /**
+         * Returns a String representation for this <code>DocId</code>.
+         *
+         * @return a String representation for this <code>DocId</code>.
+         */
+        final public String toString() {
+            return "UUIDDocId(" + uuid + ")";
+        }
+    }
+}

Propchange: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocId.java
------------------------------------------------------------------------------
    svn:eol-style = native

Added: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java?rev=234492&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java (added)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java Mon Aug 22 07:20:02 2005
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.commons.collections.map.LRUMap;
+import org.apache.log4j.Logger;
+
+/**
+ * Implements a Document number cache with a fixed size and a LRU strategy.
+ */
+final class DocNumberCache {
+
+    /**
+     * Logger instance for this class.
+     */
+    private static final Logger log = Logger.getLogger(DocNumberCache.class);
+
+    /**
+     * Log cache statistics at most every 10 seconds.
+     */
+    private static final long LOG_INTERVAL = 1000 * 10;
+
+    /**
+     * LRU Map where key=uuid value=reader;docNumber
+     */
+    private final LRUMap docNumbers;
+
+    /**
+     * Timestamp of the last cache statistics log.
+     */
+    private long lastLog;
+
+    /**
+     * Cache misses.
+     */
+    private long misses;
+
+    /**
+     * Cache accesses;
+     */
+    private long accesses;
+
+    /**
+     * Creates a new <code>DocNumberCache</code> with a limiting
+     * <code>size</code>.
+     *
+     * @param size the cache limit.
+     */
+    DocNumberCache(int size) {
+        docNumbers = new LRUMap(size);
+    }
+
+    /**
+     * Puts a document number into the cache using a uuid as key. An entry is
+     * only overwritten if the according reader is younger than the reader
+     * associated with the existing entry.
+     *
+     * @param uuid the key.
+     * @param reader the index reader from where the document number was read.
+     * @param n the document number.
+     */
+    synchronized void put(String uuid, CachingIndexReader reader, int n) {
+        Entry e = (Entry) docNumbers.get(uuid);
+        if (e != null) {
+            // existing entry
+            // ignore if reader is older than the one in entry
+            if (reader.getCreationTick() <= e.reader.getCreationTick()) {
+                if (log.isDebugEnabled()) {
+                    log.debug("Ignoring put(). New entry is not from a newer reader. " +
+                            "existing: " + e.reader.getCreationTick() +
+                            ", new: " + reader.getCreationTick());
+                }
+                e = null;
+            }
+        } else {
+            // entry did not exist
+            e = new Entry(reader, n);
+        }
+
+        if (e != null) {
+            docNumbers.put(uuid, e);
+        }
+    }
+
+    /**
+     * Returns the cache entry for <code>uuid</code>, or <code>null</code> if
+     * no entry exists for <code>uuid</code>.
+     *
+     * @param uuid the key.
+     * @return cache entry or <code>null</code>.
+     */
+    synchronized Entry get(String uuid) {
+        Entry entry = (Entry) docNumbers.get(uuid);
+        if (log.isInfoEnabled()) {
+            accesses++;
+            if (entry == null) {
+                misses++;
+            }
+            // log at most after 1000 accesses and every 10 seconds
+            if (accesses > 1000 && System.currentTimeMillis() - lastLog > LOG_INTERVAL) {
+                long ratio = 100;
+                if (misses != 0) {
+                    ratio -= misses * 100L / accesses;
+                }
+                StringBuffer statistics = new StringBuffer();
+                statistics.append("size=").append(docNumbers.size());
+                statistics.append("/").append(docNumbers.maxSize());
+                statistics.append(", #accesses=").append(accesses);
+                statistics.append(", #hits=").append((accesses - misses));
+                statistics.append(", #misses=").append(misses);
+                statistics.append(", cacheRatio=").append(ratio).append("%");
+                log.info(statistics);
+                accesses = 0;
+                misses = 0;
+                lastLog = System.currentTimeMillis();
+            }
+        }
+        return entry;
+    }
+
+    public static final class Entry {
+
+        /**
+         * The IndexReader.
+         */
+        final CachingIndexReader reader;
+
+        /**
+         * The document number.
+         */
+        final int doc;
+
+        Entry(CachingIndexReader reader, int doc) {
+            this.reader = reader;
+            this.doc = doc;
+        }
+    }
+}

Propchange: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/DocNumberCache.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java Mon Aug 22 07:20:02 2005
@@ -120,6 +120,11 @@
     private CachingMultiReader multiReader;
 
     /**
+     * Shared document number cache across all persistent indexes.
+     */
+    private final DocNumberCache cache;
+
+    /**
      * Monitor to use to synchronize access to {@link #multiReader} and
      * {@link #updateInProgress}.
      */
@@ -157,6 +162,8 @@
 
         this.indexDir = indexDir;
         this.handler = handler;
+        this.cache = new DocNumberCache(handler.getCacheSize());
+
         boolean doInitialIndex = false;
         if (indexNames.exists(indexDir)) {
             indexNames.read(indexDir);
@@ -180,7 +187,8 @@
                 if (!sub.exists() && !sub.mkdir()) {
                     throw new IOException("Unable to create directory: " + sub.getAbsolutePath());
                 }
-                PersistentIndex index = new PersistentIndex(indexNames.getName(i), sub, false, handler.getAnalyzer());
+                PersistentIndex index = new PersistentIndex(indexNames.getName(i),
+                        sub, false, handler.getAnalyzer(), cache);
                 index.setMaxMergeDocs(handler.getMaxMergeDocs());
                 index.setMergeFactor(handler.getMergeFactor());
                 index.setMinMergeDocs(handler.getMinMergeDocs());
@@ -360,12 +368,12 @@
             // some other read thread might have created the reader in the
             // meantime -> check again
             if (multiReader == null) {
-                IndexReader[] readers = new IndexReader[indexes.size() + 1];
+                ReadOnlyIndexReader[] readers = new ReadOnlyIndexReader[indexes.size() + 1];
                 for (int i = 0; i < indexes.size(); i++) {
                     readers[i] = ((PersistentIndex) indexes.get(i)).getReadOnlyIndexReader();
                 }
                 readers[readers.length - 1] = volatileIndex.getReadOnlyIndexReader();
-                multiReader = new CachingMultiReader(readers);
+                multiReader = new CachingMultiReader(readers, cache);
             }
             multiReader.incrementRefCount();
             return multiReader;
@@ -468,8 +476,10 @@
     private void internalAddDocument(Document doc) throws IOException {
         volatileIndex.addDocument(doc);
         if (volatileIndex.getRedoLog().getSize() >= handler.getMinMergeDocs()) {
-            log.info("Committing in-memory index");
+            long time = System.currentTimeMillis();
             commit();
+            time = System.currentTimeMillis() - time;
+            log.info("Committed in-memory index in " + time + "ms.");
         }
     }
 
@@ -486,7 +496,8 @@
 
             File sub = newIndexFolder();
             String name = sub.getName();
-            PersistentIndex index = new PersistentIndex(name, sub, true, handler.getAnalyzer());
+            PersistentIndex index = new PersistentIndex(name, sub, true,
+                    handler.getAnalyzer(), cache);
             index.setMaxMergeDocs(handler.getMaxMergeDocs());
             index.setMergeFactor(handler.getMergeFactor());
             index.setMinMergeDocs(handler.getMinMergeDocs());
@@ -565,7 +576,8 @@
         if (indexes.size() == 0) {
             File sub = newIndexFolder();
             String name = sub.getName();
-            PersistentIndex index = new PersistentIndex(name, sub, true, handler.getAnalyzer());
+            PersistentIndex index = new PersistentIndex(name, sub, true,
+                    handler.getAnalyzer(), cache);
             index.setMaxMergeDocs(handler.getMaxMergeDocs());
             index.setMergeFactor(handler.getMergeFactor());
             index.setMinMergeDocs(handler.getMinMergeDocs());
@@ -664,7 +676,8 @@
         // create new index
         File sub = newIndexFolder();
         String name = sub.getName();
-        PersistentIndex index = new PersistentIndex(name, sub, true, handler.getAnalyzer());
+        PersistentIndex index = new PersistentIndex(name, sub, true,
+                handler.getAnalyzer(), cache);
         index.setMaxMergeDocs(handler.getMaxMergeDocs());
         index.setMergeFactor(handler.getMergeFactor());
         index.setMinMergeDocs(handler.getMinMergeDocs());

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/PersistentIndex.java Mon Aug 22 07:20:02 2005
@@ -53,14 +53,16 @@
      * @param indexDir the directory to store the index.
      * @param create if <code>true</code> an existing index is deleted.
      * @param analyzer the analyzer for text tokenizing.
+     * @param cache the document number cache
      * @throws IOException if an error occurs while opening / creating the
      *  index.
      * @throws IOException if an error occurs while opening / creating
      *  the index.
      */
-    PersistentIndex(String name, File indexDir, boolean create, Analyzer analyzer)
+    PersistentIndex(String name, File indexDir, boolean create,
+                    Analyzer analyzer, DocNumberCache cache)
             throws IOException {
-        super(analyzer, FSDirectory.getDirectory(indexDir, create));
+        super(analyzer, FSDirectory.getDirectory(indexDir, create), cache);
         this.name = name;
 
         // check if index is locked, probably from an unclean repository

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/ReadOnlyIndexReader.java Mon Aug 22 07:20:02 2005
@@ -58,6 +58,28 @@
         reader.addClient(this);
     }
 
+    /**
+     * 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.
+     * @return the <code>DocId</code> of <code>n</code>'s parent.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    public DocId getParent(int n) throws IOException {
+        return getBase().getParent(n, deleted);
+    }
+
+    /**
+     * Returns the {@link SharedIndexReader} this reader is based on.
+     *
+     * @return the {@link SharedIndexReader} this reader is based on.
+     */
+    public SharedIndexReader getBase() {
+        return (SharedIndexReader) in;
+    }
+
     //---------------------< IndexReader overwrites >---------------------------
 
     /**

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Mon Aug 22 07:20:02 2005
@@ -124,6 +124,13 @@
     private boolean autoRepair = true;
 
     /**
+     * The uuid resolver cache size.
+     * <p/>
+     * Default value is: <code>1000</code>.
+     */
+    private int cacheSize = 1000;
+
+    /**
      * Default constructor.
      */
     public SearchIndex() {
@@ -476,5 +483,13 @@
 
     public boolean getAutoRepair() {
         return autoRepair;
+    }
+
+    public void setCacheSize(int size) {
+        cacheSize = size;
+    }
+
+    public int getCacheSize() {
+        return cacheSize;
     }
 }

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SharedIndexReader.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SharedIndexReader.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SharedIndexReader.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SharedIndexReader.java Mon Aug 22 07:20:02 2005
@@ -22,6 +22,7 @@
 
 import java.util.IdentityHashMap;
 import java.util.Map;
+import java.util.BitSet;
 import java.io.IOException;
 
 /**
@@ -54,6 +55,20 @@
     }
 
     /**
+     * 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.
+     */
+    public DocId getParent(int n, BitSet deleted) throws IOException {
+        return getBase().getParent(n, deleted);
+    }
+
+    /**
      * Registeres <code>client</code> with this reader. As long as clients are
      * registered, this shared reader will not release resources on {@link
      * #close()} and will not actually close but only marks itself to close when
@@ -106,4 +121,14 @@
     public TermDocs termDocs(Term term) throws IOException {
         return in.termDocs(term);
     }
+
+    /**
+     * Returns the {@link CachingIndexReader} this reader is based on.
+     *
+     * @return the {@link CachingIndexReader} this reader is based on.
+     */
+    public CachingIndexReader getBase() {
+        return (CachingIndexReader) in;
+    }
+
 }

Added: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java?rev=234492&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java (added)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java Mon Aug 22 07:20:02 2005
@@ -0,0 +1,112 @@
+/*
+ * Copyright 2004-2005 The Apache Software Foundation or its licensors,
+ *                     as applicable.
+ *
+ * Licensed 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.index.TermDocs;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermEnum;
+
+import java.io.IOException;
+
+/**
+ * Implements a TermDocs with a single document.
+ */
+class SingleTermDocs implements TermDocs {
+
+    /**
+     * Single document number;
+     */
+    private final int doc;
+
+    /**
+     * Flag to return the document number once.
+     */
+    private boolean next = true;
+
+    /**
+     * Creates a <code>SingleTermDocs</code> that returns <code>doc</code> as
+     * its single document.
+     *
+     * @param doc the document number.
+     */
+    SingleTermDocs(int doc) {
+        this.doc = doc;
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    public void seek(Term term) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * @throws UnsupportedOperationException always
+     */
+    public void seek(TermEnum termEnum) {
+        throw new UnsupportedOperationException();
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int doc() {
+        return doc;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int freq() {
+        return 1;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean next() throws IOException {
+        boolean hasNext = next;
+        next = false;
+        return hasNext;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public int read(int[] docs, int[] freqs) throws IOException {
+        if (next && docs.length > 0) {
+            docs[0] = doc;
+            freqs[0] = 1;
+            next = false;
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public boolean skipTo(int target) throws IOException {
+        return false;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public void close() throws IOException {
+    }
+}

Propchange: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/SingleTermDocs.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/VolatileIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/VolatileIndex.java?rev=234492&r1=234491&r2=234492&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/VolatileIndex.java (original)
+++ incubator/jackrabbit/trunk/core/src/java/org/apache/jackrabbit/core/query/lucene/VolatileIndex.java Mon Aug 22 07:20:02 2005
@@ -64,7 +64,7 @@
      * @throws IOException if an error occurs while opening the index.
      */
     VolatileIndex(Analyzer analyzer, RedoLog log) throws IOException {
-        super(analyzer, new RAMDirectory());
+        super(analyzer, new RAMDirectory(), null);
         redoLog = log;
     }