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/12/20 12:13:04 UTC

svn commit: r357961 - in /incubator/jackrabbit/trunk/jackrabbit: ./ applications/test/ applications/test/repository/ applications/test/workspaces/default/ applications/test/workspaces/test/ src/main/config/ src/main/java/org/apache/jackrabbit/core/ src...

Author: mreutegg
Date: Tue Dec 20 03:12:39 2005
New Revision: 357961

URL: http://svn.apache.org/viewcvs?rev=357961&view=rev
Log:
JCR-257: Use separate index for jcr:system tree

Added:
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java   (with props)
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java   (with props)
Modified:
    incubator/jackrabbit/trunk/jackrabbit/   (props changed)
    incubator/jackrabbit/trunk/jackrabbit/applications/test/   (props changed)
    incubator/jackrabbit/trunk/jackrabbit/applications/test/repository/   (props changed)
    incubator/jackrabbit/trunk/jackrabbit/applications/test/repository.xml
    incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/   (props changed)
    incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/test/   (props changed)
    incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandler.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryManagerImpl.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
    incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java

Propchange: incubator/jackrabbit/trunk/jackrabbit/
------------------------------------------------------------------------------
--- svn:ignore (added)
+++ svn:ignore Tue Dec 20 03:12:39 2005
@@ -0,0 +1,5 @@
+target
+*.log
+*.iws
+*.ipr
+*.iml

Propchange: incubator/jackrabbit/trunk/jackrabbit/applications/test/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Tue Dec 20 03:12:39 2005
@@ -2,3 +2,4 @@
 tx
 version
 *.log
+.lock

Propchange: incubator/jackrabbit/trunk/jackrabbit/applications/test/repository/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Tue Dec 20 03:12:39 2005
@@ -1,2 +1,3 @@
 meta
 namespaces
+index

Modified: incubator/jackrabbit/trunk/jackrabbit/applications/test/repository.xml
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/applications/test/repository.xml?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/applications/test/repository.xml (original)
+++ incubator/jackrabbit/trunk/jackrabbit/applications/test/repository.xml Tue Dec 20 03:12:39 2005
@@ -24,13 +24,13 @@
             no workspace yet and for creating additional workspaces through
             the api
 
-            a SearchIndex element that is used for configuring per workspace
-            Indexing-related settings
-
             a Versioning element that is used for configuring
             versioning-related settings
+
+            a SearchIndex element that is used for configuring Indexing-related
+            settings on the /jcr:system tree.
     -->
-    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace,Versioning)>
+    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace,Versioning,SearchIndex?)>
 
     <!--
         a virtual file system
@@ -207,6 +207,13 @@
           <param name="url" value="jdbc:derby:${rep.home}/version/db;create=true"/>
           <param name="schemaObjectPrefix" value="version_"/>
         </PersistenceManager>
-
     </Versioning>
+
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
 </Repository>

Propchange: incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/default/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Tue Dec 20 03:12:39 2005
@@ -1,4 +1,4 @@
 blobs
-data
+db
 index
 locks

Propchange: incubator/jackrabbit/trunk/jackrabbit/applications/test/workspaces/test/
------------------------------------------------------------------------------
--- svn:ignore (original)
+++ svn:ignore Tue Dec 20 03:12:39 2005
@@ -1,4 +1,4 @@
 blobs
-data
+db
 index
 locks

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/config/repository.xml Tue Dec 20 03:12:39 2005
@@ -26,13 +26,13 @@
             no workspace yet and for creating additional workspaces through
             the api
 
-            a SearchIndex element that is used for configuring per workspace
-            Indexing-related settings
-
             a Versioning element that is used for configuring
             versioning-related settings
+
+            a SearchIndex element that is used for configuring Indexing-related
+            settings on the /jcr:system tree.
     -->
-    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace,Versioning)>
+    <!ELEMENT Repository (FileSystem,Security,Workspaces,Workspace,Versioning,SearchIndex?)>
 
     <!--
         a virtual file system
@@ -258,4 +258,19 @@
         </PersistenceManager>
 
     </Versioning>
+    
+    <!--
+        Search index for content that is shared repository wide
+        (/jcr:system tree, contains mainly versions)
+        
+        The same parameters are supported as in the search index configuration
+        inside the workspace definition element.
+        
+        This element is optional. If omitted, the /jcr:system tree will not be
+        indexed and no results will be returned for that tree!
+    -->
+    <SearchIndex class="org.apache.jackrabbit.core.query.lucene.SearchIndex">
+        <param name="path" value="${rep.home}/repository/index"/>
+    </SearchIndex>
+
 </Repository>

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/RepositoryImpl.java Tue Dec 20 03:12:39 2005
@@ -41,6 +41,7 @@
 import org.apache.jackrabbit.core.version.VersionManager;
 import org.apache.jackrabbit.core.version.VersionManagerImpl;
 import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.uuid.UUID;
 import org.apache.log4j.Logger;
 
@@ -55,6 +56,7 @@
 import javax.jcr.observation.Event;
 import javax.jcr.observation.EventIterator;
 import javax.jcr.observation.EventListener;
+import javax.jcr.observation.ObservationManager;
 import javax.security.auth.Subject;
 import java.io.File;
 import java.io.IOException;
@@ -116,6 +118,12 @@
     private final VersionManager vMgr;
     private final VirtualNodeTypeStateManager virtNTMgr;
 
+    /**
+     * Search manager for the jcr:system tree. May be <code>null</code> if
+     * none is configured.
+     */
+    private SearchManager systemSearchMgr;
+
     // configuration of the repository
     protected final RepositoryConfig repConfig;
 
@@ -493,6 +501,33 @@
         delegatingDispatcher.addDispatcher(getObservationManagerFactory(wspName));
     }
 
+    /**
+     * Returns the system search manager or <code>null</code> if none is
+     * configured.
+     */
+    private SearchManager getSystemSearchManager(String wspName) throws RepositoryException {
+        if (systemSearchMgr == null) {
+            try {
+                if (repConfig.getSearchConfig() != null) {
+                    SystemSession defSysSession = getSystemSession(wspName);
+                    systemSearchMgr = new SystemSearchManager(repConfig.getSearchConfig(),
+                            nsReg, ntReg, defSysSession.getItemStateManager(), SYSTEM_ROOT_NODE_UUID);
+                    ObservationManager obsMgr = defSysSession.getWorkspace().getObservationManager();
+                    obsMgr.addEventListener(systemSearchMgr, Event.NODE_ADDED |
+                            Event.NODE_REMOVED | Event.PROPERTY_ADDED |
+                            Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED,
+                            "/" + QName.JCR_SYSTEM.toJCRName(defSysSession.getNamespaceResolver()),
+                            true, null, null, false);
+                } else {
+                    systemSearchMgr = null;
+                }
+            } catch (NoPrefixDeclaredException e) {
+                throw new RepositoryException(e);
+            }
+        }
+        return systemSearchMgr;
+    }
+
     NamespaceRegistryImpl getNamespaceRegistry() {
         // check sanity of this instance
         sanityCheck();
@@ -724,6 +759,11 @@
             wspInfo.dispose();
         }
 
+        // shutdown system search manager if there is one
+        if (systemSearchMgr != null) {
+            systemSearchMgr.close();
+        }
+
         try {
             vMgr.close();
         } catch (Exception e) {
@@ -1256,10 +1296,13 @@
                     // no search index configured
                     return null;
                 }
-                searchMgr = new SearchManager(getSystemSession(),
-                        config.getSearchConfig(),
+                searchMgr = new SearchManager(config.getSearchConfig(),
+                        nsReg,
                         ntReg,
-                        getItemStateProvider());
+                        getItemStateProvider(),
+                        rootNodeUUID,
+                        getSystemSearchManager(getName()),
+                        SYSTEM_ROOT_NODE_UUID);
             }
             return searchMgr;
         }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SearchManager.java Tue Dec 20 03:12:39 2005
@@ -30,7 +30,10 @@
 import org.apache.jackrabbit.core.state.ItemStateException;
 import org.apache.jackrabbit.core.state.ItemStateManager;
 import org.apache.jackrabbit.core.state.ItemState;
+import org.apache.jackrabbit.name.AbstractNamespaceResolver;
 import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.log4j.Logger;
 
 import javax.jcr.NamespaceException;
@@ -97,6 +100,16 @@
     private final QueryHandler handler;
 
     /**
+     * Namespace resolver that is based on the namespace registry itself.
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * Path that will be excluded from indexing.
+     */
+    private Path excludePath;
+
+    /**
      * Fully qualified name of the query implementation class.
      * This class must extend {@link org.apache.jackrabbit.core.query.AbstractQueryImpl}!
      */
@@ -104,21 +117,47 @@
 
     /**
      * Creates a new <code>SearchManager</code>.
-     * @param session the system session.
-     * @param config the search configuration.
-     * @param ntReg the node type registry.
-     * @param itemMgr the shared item state manager.
-     * @throws RepositoryException
+     *
+     * @param config           the search configuration.
+     * @param nsReg            the namespace registry.
+     * @param ntReg            the node type registry.
+     * @param itemMgr          the shared item state manager.
+     * @param rootNodeUUID     the uuid of the root node.
+     * @param parentMgr        the parent search manager or <code>null</code> if
+     *                         there is no parent search manager.
+     * @param excludedNodeUUID uuid of the node that should be excluded from
+     *                         indexing. Any descendant of that node will also
+     *                         be excluded from indexing.
+     * @throws RepositoryException if the search manager cannot be initialized
      */
-    public SearchManager(SessionImpl session,
-                         SearchConfig config,
+    public SearchManager(SearchConfig config,
+                         final NamespaceRegistry nsReg,
                          NodeTypeRegistry ntReg,
-                         ItemStateManager itemMgr) throws RepositoryException {
+                         ItemStateManager itemMgr,
+                         String rootNodeUUID,
+                         SearchManager parentMgr,
+                         String excludedNodeUUID) throws RepositoryException {
         this.fs = config.getFileSystem();
         this.itemMgr = itemMgr;
+        this.nsResolver = new AbstractNamespaceResolver() {
+            public String getURI(String prefix) throws NamespaceException {
+                try {
+                    return nsReg.getURI(prefix);
+                } catch (RepositoryException e) {
+                    throw new NamespaceException(e.getMessage());
+                }
+            }
+
+            public String getPrefix(String uri) throws NamespaceException {
+                try {
+                    return nsReg.getPrefix(uri);
+                } catch (RepositoryException e) {
+                    throw new NamespaceException(e.getMessage());
+                }
+            }
+        };
 
         // register namespaces
-        NamespaceRegistry nsReg = session.getWorkspace().getNamespaceRegistry();
         try {
             nsReg.getPrefix(NS_XS_URI);
         } catch (NamespaceException e) {
@@ -134,12 +173,22 @@
 
         queryImplClassName = config.getParameters().getProperty(PARAM_QUERY_IMPL, DEFAULT_QUERY_IMPL_CLASS);
 
+        QueryHandler parentHandler = null;
+        if (parentMgr != null) {
+            parentHandler = parentMgr.handler;
+        }
+
+        if (excludedNodeUUID != null) {
+            HierarchyManagerImpl hmgr = new HierarchyManagerImpl(rootNodeUUID, itemMgr, nsResolver);
+            excludePath = hmgr.getPath(new NodeId(excludedNodeUUID));
+        }
+
         // initialize query handler
         try {
             handler = (QueryHandler) config.newInstance();
-            NodeId rootId = (NodeId) session.getHierarchyManager().resolvePath(Path.ROOT);
             QueryHandlerContext context
-                    = new QueryHandlerContext(fs, itemMgr, rootId.getUUID(), ntReg);
+                    = new QueryHandlerContext(fs, itemMgr, rootNodeUUID, ntReg,
+                            parentHandler, excludedNodeUUID);
             handler.init(context);
         } catch (Exception e) {
             throw new RepositoryException(e.getMessage(), e);
@@ -213,6 +262,15 @@
         log.debug("onEvent: indexing started");
         long time = System.currentTimeMillis();
 
+        String exclude = "";
+        if (excludePath != null) {
+            try {
+                exclude = excludePath.toJCRPath(nsResolver);
+            } catch (NoPrefixDeclaredException e) {
+                log.error("Error filtering events.", e);
+            }
+        }
+
         // nodes that need to be removed from the index.
         Set removedNodes = new HashSet();
         // nodes that need to be added to the index.
@@ -222,6 +280,13 @@
 
         while (events.hasNext()) {
             EventImpl e = (EventImpl) events.nextEvent();
+            try {
+                if (excludePath != null && e.getPath().startsWith(exclude)) {
+                    continue;
+                }
+            } catch (RepositoryException ex) {
+                log.error("Error filtering events.", ex);
+            }
             long type = e.getType();
             if (type == Event.NODE_ADDED) {
                 addedNodes.add(e.getChildUUID());

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java?rev=357961&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java Tue Dec 20 03:12:39 2005
@@ -0,0 +1,185 @@
+/*
+ * 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;
+
+import org.apache.jackrabbit.core.config.SearchConfig;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.observation.EventImpl;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.AbstractNamespaceResolver;
+import org.apache.jackrabbit.name.NoPrefixDeclaredException;
+
+import javax.jcr.NamespaceRegistry;
+import javax.jcr.RepositoryException;
+import javax.jcr.NamespaceException;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.Event;
+import java.util.NoSuchElementException;
+
+/**
+ * <code>SystemSearchManager</code> implements a search manager for the jcr:system
+ * tree of a workspace.
+ */
+public class SystemSearchManager extends SearchManager {
+
+    /**
+     * The namespace registry of the repository.
+     */
+    private final NamespaceRegistry nsReg;
+
+    /**
+     * A system search manager will only index content that is located under
+     * /jcr:system
+     * @inheritDoc
+     */
+    public SystemSearchManager(SearchConfig config,
+                 NamespaceRegistry nsReg,
+                 NodeTypeRegistry ntReg,
+                 ItemStateManager itemMgr,
+                 String rootNodeUUID) throws RepositoryException {
+        super(config, nsReg, ntReg, itemMgr, rootNodeUUID, null, null);
+        this.nsReg = nsReg;
+    }
+
+    /**
+     * Overwrites the implementation of the base class and filters out events
+     * that are not under /jcr:system.
+     *
+     * @param events the original events.
+     */
+    public void onEvent(final EventIterator events) {
+
+        // todo FIXME use namespace resolver of session that registered this
+        // SearchManager as listener?
+        String jcrSystem = "";
+        try {
+            jcrSystem = QName.JCR_SYSTEM.toJCRName(new AbstractNamespaceResolver() {
+                public String getURI(String prefix) throws NamespaceException {
+                    try {
+                        return nsReg.getURI(prefix);
+                    } catch (RepositoryException e) {
+                        throw new NamespaceException(e.getMessage());
+                    }
+                }
+
+                public String getPrefix(String uri) throws NamespaceException {
+                    try {
+                        return nsReg.getPrefix(uri);
+                    } catch (RepositoryException e) {
+                        throw new NamespaceException(e.getMessage());
+                    }
+                }
+            });
+        } catch (NoPrefixDeclaredException e) {
+            // will never happen
+        }
+        final String jcrSystemPath = "/" + jcrSystem;
+
+        super.onEvent(new EventIterator() {
+
+            /**
+             * The next pre-fetched event. <code>null</code> if no more
+             * events are available.
+             */
+            private Event nextEvent;
+
+            /**
+             * Current position of this event iterator.
+             */
+            private long position = 0;
+
+            {
+                fetchNext();
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public Event nextEvent() {
+                if (nextEvent != null) {
+                    Event tmp = nextEvent;
+                    fetchNext();
+                    return tmp;
+                } else {
+                    throw new NoSuchElementException();
+                }
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public void skip(long skipNum) {
+                while (skipNum-- > 0) {
+                    nextEvent();
+                }
+            }
+
+            /**
+             * @return always -1
+             */
+            public long getSize() {
+                return -1;
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public long getPosition() {
+                return position;
+            }
+
+            /**
+             * @exception UnsupportedOperationException always.
+             */
+            public void remove() {
+                throw new UnsupportedOperationException("remove");
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public boolean hasNext() {
+                return nextEvent != null;
+            }
+
+            /**
+             * @inheritDoc
+             */
+            public Object next() {
+                return nextEvent();
+            }
+
+            /**
+             * Sets the next event.
+             */
+            private void fetchNext() {
+                nextEvent = null;
+                while (nextEvent == null && events.hasNext()) {
+                    EventImpl tmp = (EventImpl) events.nextEvent();
+                    try {
+                        if (tmp.getPath().startsWith(jcrSystemPath)) {
+                            nextEvent = tmp;
+                        }
+                    } catch (RepositoryException e) {
+                        // ignore and try next
+                    }
+                }
+            }
+        });
+    }
+}

Propchange: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/SystemSearchManager.java
------------------------------------------------------------------------------
    svn:eol-style = native

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/ConfigurationParser.java Tue Dec 20 03:12:39 2005
@@ -234,9 +234,12 @@
         // Versioning configuration
         VersioningConfig vc = parseVersioningConfig(root);
 
+        // Optional search configuration
+        SearchConfig sc = parseSearchConfig(root);
+
         return new RepositoryConfig(home, appName, amc, lmc, fsc,
                 workspaceDirectory, workspaceConfigDirectory, defaultWorkspace,
-                template, vc, this);
+                template, vc, sc, this);
     }
 
     /**

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/config/RepositoryConfig.java Tue Dec 20 03:12:39 2005
@@ -51,10 +51,11 @@
  * create configured repository objects.
  * <p>
  * The contained configuration information are: the home directory and name
- * of the repository, the access manager, file system, and versioning
- * configurations, the workspace directory, the default workspace name, and
- * the workspace configuration template. In addition the workspace
- * configuration object keeps track of all configured workspaces.
+ * of the repository, the access manager, file system, versioning
+ * configuration, repository index configuration, the workspace directory,
+ * the default workspace name, and the workspace configuration template. In
+ * addition the workspace configuration object keeps track of all configured
+ * workspaces.
  */
 public class RepositoryConfig {
 
@@ -206,6 +207,11 @@
     private final VersioningConfig vc;
 
     /**
+     * Optional search configuration for system search manager.
+     */
+    private final SearchConfig sc;
+
+    /**
      * Creates a repository configuration object.
      *
      * @param template workspace configuration template
@@ -218,13 +224,14 @@
      * @param workspaceConfigDirectory optional workspace configuration directory
      * @param defaultWorkspace name of the default workspace
      * @param vc versioning configuration
+     * @param sc search configuration for system search manager.
      * @param parser the ConfigurationParser that servers as config factory
      */
     RepositoryConfig(String home, String name,
             AccessManagerConfig amc, LoginModuleConfig lmc, FileSystemConfig fsc,
             String workspaceDirectory, String workspaceConfigDirectory,
             String defaultWorkspace, Element template, VersioningConfig vc,
-            ConfigurationParser parser) {
+            SearchConfig sc, ConfigurationParser parser) {
         this.workspaces = new HashMap();
         this.home = home;
         this.name = name;
@@ -236,6 +243,7 @@
         this.defaultWorkspace = defaultWorkspace;
         this.template = template;
         this.vc = vc;
+        this.sc = sc;
         this.parser = parser;
     }
 
@@ -249,6 +257,9 @@
     protected void init() throws ConfigurationException {
         fsc.init();
         vc.init();
+        if (sc != null) {
+            sc.init();
+        }
 
         // Get the physical workspace root directory (create it if not found)
         File directory = new File(workspaceDirectory);
@@ -617,4 +628,13 @@
         return vc;
     }
 
+    /**
+     * Returns the system search index configuration. Returns
+     * <code>null</code> if no search index has been configured.
+     *
+     * @return search index configuration, or <code>null</code>
+     */
+    public SearchConfig getSearchConfig() {
+        return sc;
+    }
 }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandler.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandler.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandler.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandler.java Tue Dec 20 03:12:39 2005
@@ -43,6 +43,14 @@
     void init(QueryHandlerContext context) throws IOException;
 
     /**
+     * Returns the query handler context that passed in {@link
+     * #init(QueryHandlerContext)}.
+     *
+     * @return the query handler context.
+     */
+    QueryHandlerContext getContext();
+
+    /**
      * Adds a <code>Node</code> to the search index.
      * @param node the NodeState to add.
      * @throws RepositoryException if an error occurs while indexing the node.

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryHandlerContext.java Tue Dec 20 03:12:39 2005
@@ -53,25 +53,44 @@
     private final PropertyTypeRegistry propRegistry;
 
     /**
+     * The query handler for the jcr:system tree
+     */
+    private final QueryHandler parentHandler;
+
+    /**
+     * UUID of the node that should be excluded from indexing.
+     */
+    private final String excludedNodeUUID;
+
+    /**
      * Creates a new context instance.
      *
-     * @param fs         a {@link FileSystem} this <code>QueryHandler</code> may
-     *                   use to store its index. If no <code>FileSystem</code>
-     *                   has been configured <code>fs</code> is
-     *                   <code>null</code>.
-     * @param stateMgr   provides persistent item states.
-     * @param rootUUID   the uuid of the root node.
-     * @param ntRegistry the node type registry.
+     * @param fs               a {@link FileSystem} this <code>QueryHandler</code>
+     *                         may use to store its index. If no
+     *                         <code>FileSystem</code> has been configured
+     *                         <code>fs</code> is <code>null</code>.
+     * @param stateMgr         provides persistent item states.
+     * @param rootUUID         the uuid of the root node.
+     * @param ntRegistry       the node type registry.
+     * @param parentHandler    the parent query handler or <code>null</code> it
+     *                         there is no parent handler.
+     * @param excludedNodeUUID uuid of the node that should be excluded from
+     *                         indexing. Any descendant of that node is also
+     *                         excluded from indexing.
      */
     public QueryHandlerContext(FileSystem fs,
                                ItemStateManager stateMgr,
                                String rootUUID,
-                               NodeTypeRegistry ntRegistry) {
+                               NodeTypeRegistry ntRegistry,
+                               QueryHandler parentHandler,
+                               String excludedNodeUUID) {
         this.fs = fs;
         this.stateMgr = stateMgr;
         this.rootUUID = rootUUID;
         this.ntRegistry = ntRegistry;
         propRegistry = new PropertyTypeRegistry(ntRegistry);
+        this.parentHandler = parentHandler;
+        this.excludedNodeUUID = excludedNodeUUID;
         ntRegistry.addListener(propRegistry);
     }
 
@@ -120,6 +139,24 @@
      */
     public NodeTypeRegistry getNodeTypeRegistry() {
         return ntRegistry;
+    }
+
+    /**
+     * Returns the parent query handler.
+     * @return the parent query handler.
+     */
+    public QueryHandler getParentHandler() {
+        return parentHandler;
+    }
+
+    /**
+     * Returns the uuid of the node that should be excluded from indexing. Any
+     * descendant of this node is also excluded from indexing.
+     *
+     * @return the uuid of the exluded node.
+     */
+    public String getExcludedNodeUUID() {
+        return excludedNodeUUID;
     }
 
     /**

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryManagerImpl.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryManagerImpl.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryManagerImpl.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/QueryManagerImpl.java Tue Dec 20 03:12:39 2005
@@ -83,7 +83,7 @@
      */
     public Query createQuery(String statement, String language)
             throws InvalidQueryException, RepositoryException {
-
+        sanityCheck();
         return searchMgr.createQuery(session, itemMgr, statement, language);
     }
 
@@ -92,7 +92,7 @@
      */
     public Query getQuery(Node node)
             throws InvalidQueryException, RepositoryException {
-
+        sanityCheck();
         return searchMgr.createQuery(session, itemMgr, node);
     }
 
@@ -101,5 +101,18 @@
      */
     public String[] getSupportedQueryLanguages() throws RepositoryException {
         return (String[]) SUPPORTED_QUERIES_LIST.toArray(new String[SUPPORTED_QUERIES.length]);
+    }
+
+    /**
+     * Checks if this <code>QueryManagerImpl</code> instance is still usable,
+     * otherwise throws a {@link javax.jcr.RepositoryException}.
+     *
+     * @throws RepositoryException if this query manager is not usable anymore,
+     *                             e.g. the corresponding session is closed.
+     */
+    private void sanityCheck() throws RepositoryException {
+        if (!session.isLive()) {
+            throw new RepositoryException("corresponding session has been closed");
+        }
     }
 }

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/CachingMultiReader.java Tue Dec 20 03:12:39 2005
@@ -28,7 +28,7 @@
  * Extends a <code>MultiReader</code> with support for cached <code>TermDocs</code>
  * on {@link FieldNames#UUID} field.
  */
-final class CachingMultiReader extends MultiReader {
+public final class CachingMultiReader extends MultiReader implements HierarchyResolver {
 
     /**
      * The sub readers.
@@ -90,10 +90,23 @@
      * @throws IOException if an error occurs while reading from the index.
      */
     final public int getParent(int n) throws IOException {
+        DocId id = getParentDocId(n);
+        return id.getDocumentNumber(this);
+    }
+
+    /**
+     * Returns the DocId 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 DocId of <code>n</code>'s parent.
+     * @throws IOException if an error occurs while reading from the index.
+     */
+    final public DocId getParentDocId(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);
+        return id.applyOffset(starts[i]);
     }
 
     /**

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/DescendantSelfAxisQuery.java Tue Dec 20 03:12:39 2005
@@ -170,8 +170,8 @@
         public Scorer scorer(IndexReader reader) throws IOException {
             contextScorer = contextQuery.weight(searcher).scorer(reader);
             subScorer = subQuery.weight(searcher).scorer(reader);
-            CachingMultiReader index = (CachingMultiReader) reader;
-            return new DescendantSelfAxisScorer(searcher.getSimilarity(), index);
+            HierarchyResolver resolver = (HierarchyResolver) reader;
+            return new DescendantSelfAxisScorer(searcher.getSimilarity(), reader, resolver);
         }
 
         /**
@@ -190,9 +190,9 @@
     private class DescendantSelfAxisScorer extends Scorer {
 
         /**
-         * An <code>IndexReader</code> to access the index.
+         * The <code>HierarchyResolver</code> of the index.
          */
-        private final CachingMultiReader reader;
+        private final HierarchyResolver hResolver;
 
         /**
          * BitSet storing the id's of selected documents
@@ -219,10 +219,13 @@
          *
          * @param similarity the <code>Similarity</code> instance to use.
          * @param reader     for index access.
+         * @param hResolver  the hierarchy resolver of <code>reader</code>.
          */
-        protected DescendantSelfAxisScorer(Similarity similarity, CachingMultiReader reader) {
+        protected DescendantSelfAxisScorer(Similarity similarity,
+                                           IndexReader reader,
+                                           HierarchyResolver hResolver) {
             super(similarity);
-            this.reader = reader;
+            this.hResolver = hResolver;
             // todo reuse BitSets?
             this.contextHits = new BitSet(reader.maxDoc());
             this.subHits = new BitSet(reader.maxDoc());
@@ -245,10 +248,10 @@
                 }
 
                 // check if nextDoc is a descendant of one of the context nodes
-                int parentDoc = reader.getParent(nextDoc);
+                int parentDoc = hResolver.getParent(nextDoc);
                 while (parentDoc != -1 && !contextHits.get(parentDoc)) {
                     // traverse
-                    parentDoc = reader.getParent(parentDoc);
+                    parentDoc = hResolver.getParent(parentDoc);
                 }
 
                 if (parentDoc != -1) {

Added: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java?rev=357961&view=auto
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java (added)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/HierarchyResolver.java Tue Dec 20 03:12:39 2005
@@ -0,0 +1,37 @@
+/*
+ * 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 java.io.IOException;
+
+/**
+ * <code>HierarchyResolver</code> extends an {@link org.apache.lucene.index.IndexReader}
+ * with the ability to resolve a JCR hierarchy.
+ */
+public interface HierarchyResolver {
+
+    /**
+     * 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 java.io.IOException if an error occurs while reading from the index.
+     */
+    public int getParent(int n) throws IOException;
+}

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

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/MultiIndex.java Tue Dec 20 03:12:39 2005
@@ -172,6 +172,11 @@
     private final RedoLog redoLog;
 
     /**
+     * Set&lt;String> of uuids that should not be indexed.
+     */
+    private final Set excludedUUIDs;
+
+    /**
      * The next transaction id.
      */
     private long nextTransactionId = 0;
@@ -184,21 +189,25 @@
     /**
      * Creates a new MultiIndex.
      *
-     * @param indexDir the base file system
-     * @param handler the search handler
-     * @param stateMgr shared item state manager
-     * @param rootUUID uuid of the root node
+     * @param indexDir      the base file system
+     * @param handler       the search handler
+     * @param stateMgr      shared item state manager
+     * @param rootUUID      uuid of the root node
+     * @param excludedUUIDs Set&lt;String> that contains uuids that should not
+     *                      be indexed nor further traversed.
      * @throws IOException if an error occurs
      */
     MultiIndex(File indexDir,
                SearchIndex handler,
                ItemStateManager stateMgr,
-               String rootUUID) throws IOException {
+               String rootUUID,
+               Set excludedUUIDs) throws IOException {
 
         this.indexDir = indexDir;
         this.handler = handler;
         this.cache = new DocNumberCache(handler.getCacheSize());
-        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG)); 
+        this.redoLog = new RedoLog(new File(indexDir, REDO_LOG));
+        this.excludedUUIDs = new HashSet(excludedUUIDs);
 
         if (indexNames.exists(indexDir)) {
             indexNames.read(indexDir);
@@ -817,6 +826,9 @@
     private void createIndex(NodeState node, ItemStateManager stateMgr)
             throws IOException, ItemStateException, RepositoryException {
         String uuid = node.getId().toString();
+        if (excludedUUIDs.contains(uuid)) {
+            return;
+        }
         executeAndLog(new AddNode(getTransactionId(), uuid));
         checkVolatileCommit();
         List children = node.getChildNodeEntries();

Modified: incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
URL: http://svn.apache.org/viewcvs/incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?rev=357961&r1=357960&r2=357961&view=diff
==============================================================================
--- incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original)
+++ incubator/jackrabbit/trunk/jackrabbit/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Tue Dec 20 03:12:39 2005
@@ -22,6 +22,7 @@
 import org.apache.jackrabbit.core.query.ExecutableQuery;
 import org.apache.jackrabbit.core.query.QueryHandlerContext;
 import org.apache.jackrabbit.core.query.TextFilter;
+import org.apache.jackrabbit.core.query.QueryHandler;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.QName;
@@ -31,6 +32,7 @@
 import org.apache.lucene.document.Document;
 import org.apache.lucene.index.Term;
 import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.MultiReader;
 import org.apache.lucene.search.Hits;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -47,6 +49,8 @@
 import java.util.StringTokenizer;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
 
 /**
  * Implements a {@link org.apache.jackrabbit.core.query.QueryHandler} using
@@ -178,8 +182,14 @@
         if (path == null) {
             throw new IOException("SearchIndex requires 'path' parameter in configuration!");
         }
+
+        Set excludedUUIDs = new HashSet();
+        if (context.getExcludedNodeUUID() != null) {
+            excludedUUIDs.add(context.getExcludedNodeUUID());
+        }
+
         index = new MultiIndex(new File(path), this,
-                context.getItemStateManager(), context.getRootUUID());
+                context.getItemStateManager(), context.getRootUUID(), excludedUUIDs);
         if (index.getRedoLogApplied() || forceConsistencyCheck) {
             log.info("Running consistency check...");
             try {
@@ -311,9 +321,22 @@
                                   Query query,
                                   QName[] orderProps,
                                   boolean[] orderSpecs) throws IOException {
+        QueryHandler parentHandler = getContext().getParentHandler();
+        IndexReader parentReader = null;
+        if (parentHandler instanceof SearchIndex) {
+            parentReader = ((SearchIndex) parentHandler).index.getIndexReader();
+        }
+
         SortField[] sortFields = createSortFields(orderProps, orderSpecs);
 
         IndexReader reader = index.getIndexReader();
+        if (parentReader != null) {
+            // todo FIXME not type safe
+            CachingMultiReader[] readers = {(CachingMultiReader) reader,
+                                            (CachingMultiReader) parentReader};
+            reader = new CombinedIndexReader(readers);
+        }
+
         IndexSearcher searcher = new IndexSearcher(reader);
         Hits hits;
         if (sortFields.length > 0) {
@@ -403,6 +426,77 @@
      */
     protected MultiIndex getIndex() {
         return index;
+    }
+
+    //----------------------------< internal >----------------------------------
+
+    /**
+     * Combines multiple {@link CachingMultiReader} into a <code>MultiReader</code>
+     * with {@link HierarchyResolver} support.
+     */
+    protected static final class CombinedIndexReader extends MultiReader implements HierarchyResolver {
+
+        /**
+         * The sub readers.
+         */
+        private CachingMultiReader[] subReaders;
+
+        /**
+         * Doc number starts for each sub reader
+         */
+        private int[] starts;
+
+        public CombinedIndexReader(CachingMultiReader[] indexReaders) throws IOException {
+            super(indexReaders);
+            this.subReaders = indexReaders;
+            this.starts = new int[subReaders.length + 1];
+
+            int maxDoc = 0;
+            for (int i = 0; i < subReaders.length; i++) {
+                starts[i] = maxDoc;
+                maxDoc += subReaders[i].maxDoc();
+            }
+            starts[subReaders.length] = maxDoc;
+        }
+
+        /**
+         * @inheritDoc
+         */
+        public int getParent(int n) throws IOException {
+            int i = readerIndex(n);
+            DocId id = subReaders[i].getParentDocId(n - starts[i]);
+            id = id.applyOffset(starts[i]);
+            return id.getDocumentNumber(this);
+        }
+
+        /**
+         * Returns the reader index for document <code>n</code>.
+         * Implementation copied from lucene MultiReader class.
+         *
+         * @param n document number.
+         * @return the reader index.
+         */
+        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;
+        }
+
     }
 
     //--------------------------< properties >----------------------------------