• Apache Jackrabbit源码研究(三)


    本文接着分析jackrabbit与lucene交互的核心类SearchIndex,lucene的索引与检索都是通过该类实现的

    SearchIndex类继承自抽象类AbstractQueryHandler,而抽象类AbstractQueryHandler实现了QueryHandler接口

    先浏览一下QueryHandler接口源码: 

    /**
     * Defines an interface for the actual node indexing and query execution.
     * The goal is to allow different implementations based on the persistent
     * manager in use. Some persistent model might allow to execute a query
     * in an optimized manner, e.g. database persistence.
     */
    public interface QueryHandler {
    
        /**
         * Initializes this query handler. This method is called after the
         * <code>QueryHandler</code> is instantiated.
         * <p>
         * If a file system has been configured (i.e. the fs argument is not
         * <code>null</code>), then the query handler is expected to close
         * the given file system when the {@link #close()} method is called.
         *
         * @param fs the configured search index file system, or <code>null</code>
         * @param context the context for this query handler.
         * @throws IOException if an error occurs during initialization.
         */
        void init(FileSystem fs, 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.
         * @throws IOException if an error occurs while adding the node to the index.
         */
        void addNode(NodeState node) throws RepositoryException, IOException;
    
        /**
         * Deletes the Node with <code>id</code> from the search index.
         * @param id the <code>id</code> of the node to delete.
         * @throws IOException if an error occurs while deleting the node.
         */
        void deleteNode(NodeId id) throws IOException;
    
        /**
         * Updates the index in an atomic operation. Some nodes may be removed and
         * added again in the same updateNodes() call, which is equivalent to an
         * node update.
         *
         * @param remove Iterator of <code>NodeIds</code> of nodes to delete
         * @param add    Iterator of <code>NodeState</code> instance to add to the
         *               index.
         * @throws RepositoryException if an error occurs while indexing a node.
         * @throws IOException if an error occurs while updating the index.
         */
        void updateNodes(Iterator<NodeId> remove, Iterator<NodeState> add)
                throws RepositoryException, IOException;
    
        /**
         * Closes this <code>QueryHandler</code> and frees resources attached
         * to this handler.
         */
        void close() throws IOException;
    
        /**
         * Creates a new query by specifying the query statement itself and the
         * language in which the query is stated.  If the query statement is
         * syntactically invalid, given the language specified, an
         * InvalidQueryException is thrown. <code>language</code> must specify a query language
         * string from among those returned by QueryManager.getSupportedQueryLanguages(); if it is not
         * then an <code>InvalidQueryException</code> is thrown.
         *
         * @param sessionContext component context of the current session
         * @param statement the query statement.
         * @param language the syntax of the query statement.
         * @throws InvalidQueryException if statement is invalid or language is unsupported.
         * @return A <code>Query</code> object.
         */
        ExecutableQuery createExecutableQuery(
                SessionContext sessionContext, String statement, String language)
                throws InvalidQueryException;
    
        /**
         * @return the name of the query class to use.
         */
        String getQueryClass();
    
        /**
         * Returns the ids of the nodes that refer to the node with <code>id</code>
         * by weak references.
         *
         * @param id the id of the target node.
         * @return the ids of the referring nodes.
         * @throws RepositoryException if an error occurs.
         * @throws IOException         if an error occurs while reading from the
         *                             index.
         */
        public Iterable<NodeId> getWeaklyReferringNodes(NodeId id)
                throws RepositoryException, IOException;
    }

    该接口的英文注释也说明了是用来实现node的索引与检索的,至于底层的实现方式则依赖于具体实现类(如文件方式或是数据库方式等)

    接着继续看抽象类AbstractQueryHandler的源码:

    /**
     * Implements default behaviour for some methods of {@link QueryHandler}.
     */
    public abstract class AbstractQueryHandler implements QueryHandler {
    
        /**
         * Logger instance for this class
         */
        private static final Logger log = LoggerFactory.getLogger(AbstractQueryHandler.class);
    
        /**
         * Search index file system, or <code>null</code>
         */
        protected FileSystem fs;
    
        /**
         * The context for this query handler.
         */
        private QueryHandlerContext context;
    
        /**
         * The {@link OnWorkspaceInconsistency} handler. Defaults to 'fail'.
         */
        private OnWorkspaceInconsistency owi = OnWorkspaceInconsistency.FAIL;
    
        /**
         * The name of a class that extends {@link AbstractQueryImpl}.
         */
        private String queryClass = QueryImpl.class.getName();
    
        /**
         * The max idle time for this query handler until it is stopped. This
         * property is actually not used anymore.
         */
        private String idleTime;
    
        /**
         * Initializes this query handler by setting all properties in this class
         * with appropriate parameter values.
         *
         * @param fs search index file system, or <code>null</code>
         * @param context the context for this query handler.
         */
        public final void init(FileSystem fs, QueryHandlerContext context)
                throws IOException {
            this.fs = fs;
            this.context = context;
            doInit();
        }
    
        public void close() throws IOException {
            if (fs != null) {
                try {
                    fs.close();
                } catch (FileSystemException e) {
                    throw new IOExceptionWithCause(
                            "Unable to close search index file system: " + fs, e);
                }
            }
        }
    
        /**
         * This method must be implemented by concrete sub classes and will be
         * called from {@link #init}.
         *
         * @throws IOException If an error occurs.
         */
        protected abstract void doInit() throws IOException;
    
        /**
         * Returns the context for this query handler.
         *
         * @return the <code>QueryHandlerContext</code> instance for this
         *         <code>QueryHandler</code>.
         */
        public QueryHandlerContext getContext() {
            return context;
        }
    
        /**
         * This default implementation calls the individual {@link #deleteNode(org.apache.jackrabbit.core.NodeId)}
         * and {@link #addNode(org.apache.jackrabbit.core.state.NodeState)} methods
         * for each entry in the iterators. First the nodes to remove are processed
         * then the nodes to add.
         *
         * @param remove uuids of nodes to remove.
         * @param add NodeStates to add.
         * @throws RepositoryException if an error occurs while indexing a node.
         * @throws IOException if an error occurs while updating the index.
         */
        public synchronized void updateNodes(
                Iterator<NodeId> remove, Iterator<NodeState> add)
                throws RepositoryException, IOException {
            while (remove.hasNext()) {
                deleteNode(remove.next());
            }
            while (add.hasNext()) {
                addNode(add.next());
            }
        }
    
        /**
         * @return the {@link OnWorkspaceInconsistency} handler.
         */
        public OnWorkspaceInconsistency getOnWorkspaceInconsistencyHandler() {
            return owi;
        }
    
        //--------------------------< properties >----------------------------------
    
        /**
         * Sets the {@link OnWorkspaceInconsistency} handler with the given name.
         * Currently the valid names are:
         * <ul>
         * <li><code>fail</code></li>
         * <li><code>log</code></li>
         * </ul>
         *
         * @param name the name of a {@link OnWorkspaceInconsistency} handler.
         */
        public void setOnWorkspaceInconsistency(String name) {
            owi = OnWorkspaceInconsistency.fromString(name);
        }
    
        /**
         * @return the name of the currently set {@link OnWorkspaceInconsistency}.
         */
        public String getOnWorkspaceInconsistency() {
            return owi.getName();
        }
    
        /**
         * Sets the name of the query class to use.
         *
         * @param queryClass the name of the query class to use.
         */
        public void setQueryClass(String queryClass) {
            this.queryClass = queryClass;
        }
    
        /**
         * @return the name of the query class to use.
         */
        public String getQueryClass() {
            return queryClass;
        }
    
        /**
         * Sets the query handler idle time.
         * @deprecated
         * This parameter is not supported any more.
         * Please use 'maxIdleTime' in the repository configuration.
         * 
         * @param idleTime the query handler idle time.
         */
        public void setIdleTime(String idleTime) {
            log.warn("Parameter 'idleTime' is not supported anymore. "
                    + "Please use 'maxIdleTime' in the repository configuration.");
            this.idleTime = idleTime;
        }
    
        /**
         * @return the query handler idle time.
         */
        public String getIdleTime() {
            return idleTime;
        }
    
    }

    抽象类可以不必实现接口的所有方法,英文注释说明该类提供了QueryHandler接口的部分方法的默认实现

    同时在它的void init(FileSystem fs, QueryHandlerContext context)方法里面调用了访问模式为proteced类型的doInit()方法,这里的doInit()方法相当于模板方法,强制子类实现

    其他的方法则是QueryHandler接口方法的默认实现

    最后到真正的实现类SearchIndex的源码:

    /**
     * Implements a {@link org.apache.jackrabbit.core.query.QueryHandler} using
     * Lucene.
     */
    public class SearchIndex extends AbstractQueryHandler {
    
        /**
         * Valid node type names under /jcr:system. Used to determine if a
         * query needs to be executed also against the /jcr:system tree.
         */
        public static final Collection<Name> VALID_SYSTEM_INDEX_NODE_TYPE_NAMES =
            Collections.unmodifiableCollection(Arrays.asList(
                    NameConstants.NT_CHILDNODEDEFINITION,
                    NameConstants.NT_FROZENNODE,
                    NameConstants.NT_NODETYPE,
                    NameConstants.NT_PROPERTYDEFINITION,
                    NameConstants.NT_VERSION,
                    NameConstants.NT_VERSIONEDCHILD,
                    NameConstants.NT_VERSIONHISTORY,
                    NameConstants.NT_VERSIONLABELS,
                    NameConstants.REP_NODETYPES,
                    NameConstants.REP_SYSTEM,
                    NameConstants.REP_VERSIONSTORAGE,
                    // Supertypes
                    NameConstants.NT_BASE,
                    NameConstants.MIX_REFERENCEABLE));
            
        /**
         * Default query node factory.
         */
        private static final DefaultQueryNodeFactory DEFAULT_QUERY_NODE_FACTORY =
            new DefaultQueryNodeFactory(VALID_SYSTEM_INDEX_NODE_TYPE_NAMES);
    
        /** The logger instance for this class */
        private static final Logger log = LoggerFactory.getLogger(SearchIndex.class);
    
        /**
         * Name of the file to persist search internal namespace mappings.
         */
        private static final String NS_MAPPING_FILE = "ns_mappings.properties";
    
        /**
         * The default value for property {@link #minMergeDocs}.
         */
        public static final int DEFAULT_MIN_MERGE_DOCS = 100;
    
        /**
         * The default value for property {@link #maxMergeDocs}.
         */
        public static final int DEFAULT_MAX_MERGE_DOCS = Integer.MAX_VALUE;
    
        /**
         * the default value for property {@link #mergeFactor}.
         */
        public static final int DEFAULT_MERGE_FACTOR = 10;
    
        /**
         * the default value for property {@link #maxFieldLength}.
         */
        public static final int DEFAULT_MAX_FIELD_LENGTH = 10000;
    
        /**
         * The default value for property {@link #extractorPoolSize}.
         * @deprecated this value is not used anymore. Instead the default value
         * is calculated as follows: 2 * Runtime.getRuntime().availableProcessors().
         */
        public static final int DEFAULT_EXTRACTOR_POOL_SIZE = 0;
    
        /**
         * The default value for property {@link #extractorBackLog}.
         */
        public static final int DEFAULT_EXTRACTOR_BACK_LOG = Integer.MAX_VALUE;
    
        /**
         * The default timeout in milliseconds which is granted to the text
         * extraction process until fulltext indexing is deferred to a background
         * thread.
         */
        public static final long DEFAULT_EXTRACTOR_TIMEOUT = 100;
    
        /**
         * The default value for {@link #termInfosIndexDivisor}.
         */
        public static final int DEFAULT_TERM_INFOS_INDEX_DIVISOR = 1;
    
        /**
         * The path factory.
         */
        protected static final PathFactory PATH_FACTORY = PathFactoryImpl.getInstance();
    
        /**
         * The path of the root node.
         */
        protected static final Path ROOT_PATH;
    
        /**
         * The path <code>/jcr:system</code>.
         */
        protected static final Path JCR_SYSTEM_PATH;
    
        static {
            ROOT_PATH = PATH_FACTORY.create(NameConstants.ROOT);
            try {
                JCR_SYSTEM_PATH = PATH_FACTORY.create(ROOT_PATH, NameConstants.JCR_SYSTEM, false);
            } catch (RepositoryException e) {
                // should never happen, path is always valid
                throw new InternalError(e.getMessage());
            }
        }
    
        /**
         * The actual index
         */
        protected MultiIndex index;
    
        /**
         * The analyzer we use for indexing.
         */
        private final JackrabbitAnalyzer analyzer = new JackrabbitAnalyzer();
    
        /**
         * The parser for extracting text content from binary properties.
         */
        private final JackrabbitParser parser = new JackrabbitParser();
    
        /**
         * The namespace mappings used internally.
         */
        private NamespaceMappings nsMappings;
    
        /**
         * The location of the search index.
         * <p/>
         * Note: This is a <b>mandatory</b> parameter!
         */
        private String path;
    
        /**
         * minMergeDocs config parameter.
         */
        private int minMergeDocs = DEFAULT_MIN_MERGE_DOCS;
    
        /**
         * The maximum volatile index size in bytes until it is written to disk.
         * The default value is 1048576 (1MB).
         */
        private long maxVolatileIndexSize = 1024 * 1024;
    
        /**
         * volatileIdleTime config parameter.
         */
        private int volatileIdleTime = 3;
    
        /**
         * The maximum age (in seconds) of the index history. The default value is
         * zero. Which means, index commits are deleted as soon as they are not used
         * anymore.
         */
        private long maxHistoryAge = 0;
    
        /**
         * maxMergeDocs config parameter
         */
        private int maxMergeDocs = DEFAULT_MAX_MERGE_DOCS;
    
        /**
         * mergeFactor config parameter
         */
        private int mergeFactor = DEFAULT_MERGE_FACTOR;
    
        /**
         * maxFieldLength config parameter
         */
        private int maxFieldLength = DEFAULT_MAX_FIELD_LENGTH;
    
        /**
         * maxExtractLength config parameter. Positive values are used as-is,
         * negative values are interpreted as factors of the maxFieldLength
         * parameter.
         */
        private int maxExtractLength = -10;
    
        /**
         * extractorPoolSize config parameter
         */
        private int extractorPoolSize = 2 * Runtime.getRuntime().availableProcessors();
    
        /**
         * extractorBackLog config parameter
         */
        private int extractorBackLog = DEFAULT_EXTRACTOR_BACK_LOG;
    
        /**
         * extractorTimeout config parameter
         */
        private long extractorTimeout = DEFAULT_EXTRACTOR_TIMEOUT;
    
        /**
         * Number of documents that are buffered before they are added to the index.
         */
        private int bufferSize = 10;
    
        /**
         * Compound file flag
         */
        private boolean useCompoundFile = true;
    
        /**
         * Flag indicating whether document order is enabled as the default
         * ordering.
         * <p/>
         * Default value is: <code>false</code>.
         */
        private boolean documentOrder = false;
    
        /**
         * If set <code>true</code> the index is checked for consistency on startup.
         * If <code>false</code> a consistency check is only performed when there
         * are entries in the redo log on startup.
         * <p/>
         * Default value is: <code>false</code>.
         */
        private boolean forceConsistencyCheck = false;
    
        /**
         * If set <code>true</code> the index is checked for consistency depending
         * on the {@link #forceConsistencyCheck} parameter. If set to
         * <code>false</code>, no consistency check is performed, even if the redo
         * log had been applied on startup.
         * <p/>
         * Default value is: <code>false</code>.
         */
        private boolean consistencyCheckEnabled = false;
    
        /**
         * If set <code>true</code> errors detected by the consistency check are
         * repaired. If <code>false</code> the errors are only reported in the log.
         * <p/>
         * Default value is: <code>true</code>.
         */
        private boolean autoRepair = true;
    
        /**
         * The id resolver cache size.
         * <p/>
         * Default value is: <code>1000</code>.
         */
        private int cacheSize = 1000;
    
        /**
         * The number of documents that are pre fetched when a query is executed.
         * <p/>
         * Default value is: {@link Integer#MAX_VALUE}.
         */
        private int resultFetchSize = Integer.MAX_VALUE;
    
        /**
         * If set to <code>true</code> the fulltext field is stored and and a term
         * vector is created with offset information.
         * <p/>
         * Default value is: <code>false</code>.
         */
        private boolean supportHighlighting = false;
    
        /**
         * The excerpt provider class. Implements {@link ExcerptProvider}.
         */
        private Class<?> excerptProviderClass = DefaultHTMLExcerpt.class;
    
        /**
         * The path to the indexing configuration file (can be an absolute path to a
         * file or a classpath resource).
         */
        private String indexingConfigPath;
    
        /**
         * The DOM with the indexing configuration or <code>null</code> if there
         * is no such configuration.
         */
        private Element indexingConfiguration;
    
        /**
         * The indexing configuration.
         */
        private IndexingConfiguration indexingConfig;
    
        /**
         * The indexing configuration class.
         * Implements {@link IndexingConfiguration}.
         */
        private Class<?> indexingConfigurationClass = IndexingConfigurationImpl.class;
    
        /**
         * The class that implements {@link SynonymProvider}.
         */
        private Class<?> synonymProviderClass;
    
        /**
         * The currently set synonym provider.
         */
        private SynonymProvider synProvider;
    
        /**
         * The configuration path for the synonym provider.
         */
        private String synonymProviderConfigPath;
    
        /**
         * The FileSystem for the synonym if the query handler context does not
         * provide one.
         */
        private FileSystem synonymProviderConfigFs;
    
        /**
         * Indicates the index format version which is relevant to a <b>query</b>. This
         * value may be different from what {@link MultiIndex#getIndexFormatVersion()}
         * returns because queries may be executed on two physical indexes with
         * different formats. Index format versions are considered backward
         * compatible. That is, the lower version of the two physical indexes is
         * used for querying.
         */
        private IndexFormatVersion indexFormatVersion;
    
        /**
         * The class that implements {@link SpellChecker}.
         */
        private Class<?> spellCheckerClass;
    
        /**
         * The spell checker for this query handler or <code>null</code> if none is
         * configured.
         */
        private SpellChecker spellChecker;
    
        /**
         * The similarity in use for indexing and searching.
         */
        private Similarity similarity = Similarity.getDefault();
    
        /**
         * The name of the directory manager class implementation.
         */
        private String directoryManagerClass = FSDirectoryManager.class.getName();
    
        /**
         * The directory manager.
         */
        private DirectoryManager directoryManager;
    
        /**
         * The termInfosIndexDivisor.
         */
        private int termInfosIndexDivisor = DEFAULT_TERM_INFOS_INDEX_DIVISOR;
    
        /**
         * The sort comparator source for indexed properties.
         */
        private SortComparatorSource scs;
    
        /**
         * Flag that indicates whether the hierarchy cache should be initialized
         * immediately on startup.
         */
        private boolean initializeHierarchyCache = true;
    
        /**
         * The name of the redo log factory class implementation.
         */
        private String redoLogFactoryClass = DefaultRedoLogFactory.class.getName();
    
        /**
         * The redo log factory.
         */
        private RedoLogFactory redoLogFactory;
    
        /**
         * Indicates if this <code>SearchIndex</code> is closed and cannot be used
         * anymore.
         */
        private boolean closed = false;
    
        /**
         * Initializes this <code>QueryHandler</code>. This implementation requires
         * that a path parameter is set in the configuration. If this condition
         * is not met, a <code>IOException</code> is thrown.
         *
         * @throws IOException if an error occurs while initializing this handler.
         */
        protected void doInit() throws IOException {
            QueryHandlerContext context = getContext();
            if (path == null) {
                throw new IOException("SearchIndex requires 'path' parameter in configuration!");
            }
    
            Set<NodeId> excludedIDs = new HashSet<NodeId>();
            if (context.getExcludedNodeId() != null) {
                excludedIDs.add(context.getExcludedNodeId());
            }
    
            synProvider = createSynonymProvider();
            directoryManager = createDirectoryManager();
            redoLogFactory = createRedoLogFactory();
    
            if (context.getParentHandler() instanceof SearchIndex) {
                // use system namespace mappings
                SearchIndex sysIndex = (SearchIndex) context.getParentHandler();
                nsMappings = sysIndex.getNamespaceMappings();
            } else {
                // read local namespace mappings
                File mapFile = new File(new File(path), NS_MAPPING_FILE);
                if (mapFile.exists()) {
                    // be backward compatible and use ns_mappings.properties from
                    // index folder
                    nsMappings = new FileBasedNamespaceMappings(mapFile);
                } else {
                    // otherwise use repository wide stable index prefix from
                    // namespace registry
                    nsMappings = new NSRegistryBasedNamespaceMappings(
                            context.getNamespaceRegistry());
                }
            }
    
            scs = new SharedFieldSortComparator(
                    FieldNames.PROPERTIES, context.getItemStateManager(),
                    context.getHierarchyManager(), nsMappings);
            indexingConfig = createIndexingConfiguration(nsMappings);
            analyzer.setIndexingConfig(indexingConfig);
    
            index = new MultiIndex(this, excludedIDs);
            if (index.numDocs() == 0) {
                Path rootPath;
                if (excludedIDs.isEmpty()) {
                    // this is the index for jcr:system
                    rootPath = JCR_SYSTEM_PATH;
                } else {
                    rootPath = ROOT_PATH;
                }
                index.createInitialIndex(context.getItemStateManager(),
                        context.getRootId(), rootPath);
            }
            if (consistencyCheckEnabled
                    && (index.getRedoLogApplied() || forceConsistencyCheck)) {
                log.info("Running consistency check...");
                try {
                    ConsistencyCheck check = runConsistencyCheck();
                    if (autoRepair) {
                        check.repair(true);
                    } else {
                        List<ConsistencyCheckError> errors = check.getErrors();
                        if (errors.size() == 0) {
                            log.info("No errors detected.");
                        }
                        for (ConsistencyCheckError err : errors) {
                            log.info(err.toString());
                        }
                    }
                } catch (Exception e) {
                    log.warn("Failed to run consistency check on index: " + e);
                }
            }
    
            // initialize spell checker
            spellChecker = createSpellChecker();
    
            log.info("Index initialized: {} Version: {}",
                    new Object[]{path, index.getIndexFormatVersion()});
            if (!index.getIndexFormatVersion().equals(getIndexFormatVersion())) {
                log.warn("Using Version {} for reading. Please re-index version " +
                        "storage for optimal performance.",
                        getIndexFormatVersion().getVersion());
            }
        }
    
        /**
         * Adds the <code>node</code> to the search index.
         * @param node the node to add.
         * @throws RepositoryException if an error occurs while indexing the node.
         * @throws IOException if an error occurs while adding the node to the index.
         */
        public void addNode(NodeState node) throws RepositoryException, IOException {
            throw new UnsupportedOperationException("addNode");
        }
    
        /**
         * Removes the node with <code>id</code> from the search index.
         * @param id the id of the node to remove from the index.
         * @throws IOException if an error occurs while removing the node from
         * the index.
         */
        public void deleteNode(NodeId id) throws IOException {
            throw new UnsupportedOperationException("deleteNode");
        }
    
        /**
         * This implementation forwards the call to
         * {@link MultiIndex#update(Collection, Collection)} and
         * transforms the two iterators to the required types.
         *
         * @param remove ids of nodes to remove.
         * @param add    NodeStates to add. Calls to <code>next()</code> on this
         *               iterator may return <code>null</code>, to indicate that a
         *               node could not be indexed successfully.
         * @throws RepositoryException if an error occurs while indexing a node.
         * @throws IOException         if an error occurs while updating the index.
         */
        public void updateNodes(Iterator<NodeId> remove, Iterator<NodeState> add)
                throws RepositoryException, IOException {
            checkOpen();
    
            Map<NodeId, NodeState> aggregateRoots = new HashMap<NodeId, NodeState>();
            Set<NodeId> removedIds = new HashSet<NodeId>();
            Set<NodeId> addedIds = new HashSet<NodeId>();
    
            Collection<NodeId> removeCollection = new ArrayList<NodeId>();
            while (remove.hasNext()) {
                NodeId id = remove.next();
                removeCollection.add(id);
                removedIds.add(id);
            }
    
            Collection<Document> addCollection = new ArrayList<Document>();
            while (add.hasNext()) {
                NodeState state = add.next();
                if (state != null) {
                    NodeId id = state.getNodeId();
                    addedIds.add(id);
                    removedIds.remove(id);
                    retrieveAggregateRoot(state, aggregateRoots);
    
                    try {
                        addCollection.add(createDocument(
                                state, getNamespaceMappings(),
                                index.getIndexFormatVersion()));
                    } catch (RepositoryException e) {
                        log.warn("Exception while creating document for node: "
                                + state.getNodeId() + ": " + e.toString());
                    }
                }
            }
    
            index.update(removeCollection, addCollection);
    
            // remove any aggregateRoot nodes that are new
            // and therefore already up-to-date
            aggregateRoots.keySet().removeAll(addedIds);
    
            // based on removed ids get affected aggregate root nodes
            retrieveAggregateRoot(removedIds, aggregateRoots);
    
            // update aggregates if there are any affected
            if (!aggregateRoots.isEmpty()) {
                Collection<Document> modified =
                    new ArrayList<Document>(aggregateRoots.size());
    
                for (NodeState state : aggregateRoots.values()) {
                    try {
                        modified.add(createDocument(
                                state, getNamespaceMappings(),
                                index.getIndexFormatVersion()));
                    } catch (RepositoryException e) {
                        log.warn("Exception while creating document for node: "
                                + state.getNodeId(), e);
                    }
                }
    
                index.update(aggregateRoots.keySet(), modified);
            }
        }
    
        /**
         * Creates a new query by specifying the query statement itself and the
         * language in which the query is stated.  If the query statement is
         * syntactically invalid, given the language specified, an
         * InvalidQueryException is thrown. <code>language</code> must specify a query language
         * string from among those returned by QueryManager.getSupportedQueryLanguages(); if it is not
         * then an <code>InvalidQueryException</code> is thrown.
         *
         * @param sessionContext component context of the current session
         * @param statement the query statement.
         * @param language the syntax of the query statement.
         * @throws InvalidQueryException if statement is invalid or language is unsupported.
         * @return A <code>Query</code> object.
         */
        public ExecutableQuery createExecutableQuery(
                SessionContext sessionContext, String statement, String language)
                throws InvalidQueryException {
            QueryImpl query = new QueryImpl(
                    sessionContext, this, getContext().getPropertyTypeRegistry(),
                    statement, language, getQueryNodeFactory());
            query.setRespectDocumentOrder(documentOrder);
            return query;
        }
    
        /**
         * {@inheritDoc}
         */
        public Iterable<NodeId> getWeaklyReferringNodes(NodeId id)
                throws RepositoryException, IOException {
            final List<Integer> docs = new ArrayList<Integer>();
            final List<NodeId> ids = new ArrayList<NodeId>();
            final IndexReader reader = getIndexReader();
            try {
                IndexSearcher searcher = new IndexSearcher(reader);
                try {
                    Query q = new TermQuery(new Term(
                            FieldNames.WEAK_REFS, id.toString()));
                    searcher.search(q, new HitCollector() {
                        public void collect(int doc, float score) {
                            docs.add(doc);
                        }
                    });
                } finally {
                    searcher.close();
                }
                for (Integer doc : docs) {
                    Document d = reader.document(doc, FieldSelectors.UUID);
                    ids.add(new NodeId(d.get(FieldNames.UUID)));
                }
            } finally {
                Util.closeOrRelease(reader);
            }
            return ids;
        }
    
        /**
         * This method returns the QueryNodeFactory used to parse Queries. This method
         * may be overridden to provide a customized QueryNodeFactory
         *
         * @return the query node factory.
         */
        protected DefaultQueryNodeFactory getQueryNodeFactory() {
            return DEFAULT_QUERY_NODE_FACTORY;
        }
    
        /**
         * Waits until all pending text extraction tasks have been processed
         * and the updated index has been flushed to disk.
         *
         * @throws RepositoryException if the index update can not be written
         */
        public void flush() throws RepositoryException {
            try {
                index.waitUntilIndexingQueueIsEmpty();
                index.flush();
                // flush may have pushed nodes into the indexing queue
                // -> wait again
                index.waitUntilIndexingQueueIsEmpty();
            } catch (IOException e) {
                throw new RepositoryException("Failed to flush the index", e);
            }
        }
    
        /**
         * Closes this <code>QueryHandler</code> and frees resources attached
         * to this handler.
         */
        public void close() throws IOException {
            if (synonymProviderConfigFs != null) {
                try {
                    synonymProviderConfigFs.close();
                } catch (FileSystemException e) {
                    log.warn("Exception while closing FileSystem", e);
                }
            }
            if (spellChecker != null) {
                spellChecker.close();
            }
            index.close();
            getContext().destroy();
            super.close();
            closed = true;
            log.info("Index closed: " + path);
        }
    
        /**
         * Executes the query on the search index.
         *
         * @param session         the session that executes the query.
         * @param queryImpl       the query impl.
         * @param query           the lucene query.
         * @param orderProps      name of the properties for sort order.
         * @param orderSpecs      the order specs for the sort order properties.
         *                        <code>true</code> indicates ascending order,
         *                        <code>false</code> indicates descending.
         * @param resultFetchHint a hint on how many results should be fetched.
         * @return the query hits.
         * @throws IOException if an error occurs while searching the index.
         */
        public MultiColumnQueryHits executeQuery(SessionImpl session,
                                                 AbstractQueryImpl queryImpl,
                                                 Query query,
                                                 Path[] orderProps,
                                                 boolean[] orderSpecs,
                                                 long resultFetchHint)
                throws IOException {
            checkOpen();
    
            Sort sort = new Sort(createSortFields(orderProps, orderSpecs));
    
            final IndexReader reader = getIndexReader(queryImpl.needsSystemTree());
            JackrabbitIndexSearcher searcher = new JackrabbitIndexSearcher(
                    session, reader, getContext().getItemStateManager());
            searcher.setSimilarity(getSimilarity());
            return new FilterMultiColumnQueryHits(
                    searcher.execute(query, sort, resultFetchHint,
                            QueryImpl.DEFAULT_SELECTOR_NAME)) {
                public void close() throws IOException {
                    try {
                        super.close();
                    } finally {
                        Util.closeOrRelease(reader);
                    }
                }
            };
        }
    
        /**
         * Executes the query on the search index.
         *
         * @param session         the session that executes the query.
         * @param query           the query.
         * @param orderings       the order specs for the sort order.
         * @param resultFetchHint a hint on how many results should be fetched.
         * @return the query hits.
         * @throws IOException if an error occurs while searching the index.
         */
        public MultiColumnQueryHits executeQuery(SessionImpl session,
                                                 MultiColumnQuery query,
                                                 Ordering[] orderings,
                                                 long resultFetchHint)
                throws IOException {
            checkOpen();
    
            final IndexReader reader = getIndexReader();
            JackrabbitIndexSearcher searcher = new JackrabbitIndexSearcher(
                    session, reader, getContext().getItemStateManager());
            searcher.setSimilarity(getSimilarity());
            return new FilterMultiColumnQueryHits(
                    query.execute(searcher, orderings, resultFetchHint)) {
                public void close() throws IOException {
                    try {
                        super.close();
                    } finally {
                        Util.closeOrRelease(reader);
                    }
                }
            };
        }
    
        /**
         * Creates an excerpt provider for the given <code>query</code>.
         *
         * @param query the query.
         * @return an excerpt provider for the given <code>query</code>.
         * @throws IOException if the provider cannot be created.
         */
        public ExcerptProvider createExcerptProvider(Query query)
                throws IOException {
            ExcerptProvider ep;
            try {
                ep = (ExcerptProvider) excerptProviderClass.newInstance();
            } catch (Exception e) {
                throw Util.createIOException(e);
            }
            ep.init(query, this);
            return ep;
        }
    
        /**
         * Returns the analyzer in use for indexing.
         * @return the analyzer in use for indexing.
         */
        public Analyzer getTextAnalyzer() {
            return analyzer;
        }
    
        /**
         * Returns the parser used for extracting text content
         * from binary properties for full text indexing.
         *
         * @return the configured parser
         */
        public Parser getParser() {
            return parser;
        }
    
        /**
         * Returns the namespace mappings for the internal representation.
         * @return the namespace mappings for the internal representation.
         */
        public NamespaceMappings getNamespaceMappings() {
            return nsMappings;
        }
    
        /**
         * @return the indexing configuration or <code>null</code> if there is
         *         none.
         */
        public IndexingConfiguration getIndexingConfig() {
            return indexingConfig;
        }
    
        /**
         * @return the synonym provider of this search index. If none is set for
         *         this search index the synonym provider of the parent handler is
         *         returned if there is any.
         */
        public SynonymProvider getSynonymProvider() {
            if (synProvider != null) {
                return synProvider;
            } else {
                QueryHandler handler = getContext().getParentHandler();
                if (handler instanceof SearchIndex) {
                    return ((SearchIndex) handler).getSynonymProvider();
                } else {
                    return null;
                }
            }
        }
    
        /**
         * @return the spell checker of this search index. If none is configured
         *         this method returns <code>null</code>.
         */
        public SpellChecker getSpellChecker() {
            return spellChecker;
        }
    
        /**
         * @return the similarity, which should be used for indexing and searching.
         */
        public Similarity getSimilarity() {
            return similarity;
        }
    
        /**
         * Returns an index reader for this search index. The caller of this method
         * is responsible for closing the index reader when he is finished using
         * it.
         *
         * @return an index reader for this search index.
         * @throws IOException the index reader cannot be obtained.
         */
        public IndexReader getIndexReader() throws IOException {
            return getIndexReader(true);
        }
    
        /**
         * Returns the index format version that this search index is able to
         * support when a query is executed on this index.
         *
         * @return the index format version for this search index.
         */
        public IndexFormatVersion getIndexFormatVersion() {
            if (indexFormatVersion == null) {
                if (getContext().getParentHandler() instanceof SearchIndex) {
                    SearchIndex parent = (SearchIndex) getContext().getParentHandler();
                    if (parent.getIndexFormatVersion().getVersion()
                            < index.getIndexFormatVersion().getVersion()) {
                        indexFormatVersion = parent.getIndexFormatVersion();
                    } else {
                        indexFormatVersion = index.getIndexFormatVersion();
                    }
                } else {
                    indexFormatVersion = index.getIndexFormatVersion();
                }
            }
            return indexFormatVersion;
        }
    
        /**
         * @return the directory manager for this search index.
         */
        public DirectoryManager getDirectoryManager() {
            return directoryManager;
        }
    
        /**
         * @return the redo log factory for this search index.
         */
        public RedoLogFactory getRedoLogFactory() {
            return redoLogFactory;
        }
    
        /**
         * Runs a consistency check on this search index.
         *
         * @return the result of the consistency check.
         * @throws IOException if an error occurs while running the check.
         */
        public ConsistencyCheck runConsistencyCheck() throws IOException {
            return index.runConsistencyCheck();
        }
    
        /**
         * Returns an index reader for this search index. The caller of this method
         * is responsible for closing the index reader when he is finished using
         * it.
         *
         * @param includeSystemIndex if <code>true</code> the index reader will
         *                           cover the complete workspace. If
         *                           <code>false</code> the returned index reader
         *                           will not contains any nodes under /jcr:system.
         * @return an index reader for this search index.
         * @throws IOException the index reader cannot be obtained.
         */
        protected IndexReader getIndexReader(boolean includeSystemIndex)
                throws IOException {
            QueryHandler parentHandler = getContext().getParentHandler();
            CachingMultiIndexReader parentReader = null;
            if (parentHandler instanceof SearchIndex && includeSystemIndex) {
                parentReader = ((SearchIndex) parentHandler).index.getIndexReader();
            }
    
            IndexReader reader;
            if (parentReader != null) {
                CachingMultiIndexReader[] readers = {index.getIndexReader(), parentReader};
                reader = new CombinedIndexReader(readers);
            } else {
                reader = index.getIndexReader();
            }
            return new JackrabbitIndexReader(reader);
        }
    
        /**
         * Creates the SortFields for the order properties.
         *
         * @param orderProps the order properties.
         * @param orderSpecs the order specs for the properties.
         * @return an array of sort fields
         */
        protected SortField[] createSortFields(Path[] orderProps,
                                               boolean[] orderSpecs) {
            List<SortField> sortFields = new ArrayList<SortField>();
            for (int i = 0; i < orderProps.length; i++) {
                if (orderProps[i].getLength() == 1
                        && NameConstants.JCR_SCORE.equals(orderProps[i].getName())) {
                    // order on jcr:score does not use the natural order as
                    // implemented in lucene. score ascending in lucene means that
                    // higher scores are first. JCR specs that lower score values
                    // are first.
                    sortFields.add(new SortField(null, SortField.SCORE, orderSpecs[i]));
                } else {
                    sortFields.add(new SortField(orderProps[i].getString(), scs, !orderSpecs[i]));
                }
            }
            return sortFields.toArray(new SortField[sortFields.size()]);
        }
    
        /**
         * Creates internal orderings for the QOM ordering specifications.
         *
         * @param orderings the QOM ordering specifications.
         * @return the internal orderings.
         * @throws RepositoryException if an error occurs.
         */
        protected Ordering[] createOrderings(OrderingImpl[] orderings)
                throws RepositoryException {
            Ordering[] ords = new Ordering[orderings.length];
            for (int i = 0; i < orderings.length; i++) {
                ords[i] = Ordering.fromQOM(orderings[i], scs, nsMappings);
            }
            return ords;
        }
    
        /**
         * Creates a lucene <code>Document</code> for a node state using the
         * namespace mappings <code>nsMappings</code>.
         *
         * @param node               the node state to index.
         * @param nsMappings         the namespace mappings of the search index.
         * @param indexFormatVersion the index format version that should be used to
         *                           index the passed node state.
         * @return a lucene <code>Document</code> that contains all properties of
         *         <code>node</code>.
         * @throws RepositoryException if an error occurs while indexing the
         *                             <code>node</code>.
         */
        protected Document createDocument(NodeState node,
                                          NamespaceMappings nsMappings,
                                          IndexFormatVersion indexFormatVersion)
                throws RepositoryException {
            NodeIndexer indexer = new NodeIndexer(
                    node, getContext().getItemStateManager(), nsMappings,
                    getContext().getExecutor(), parser);
            indexer.setSupportHighlighting(supportHighlighting);
            indexer.setIndexingConfiguration(indexingConfig);
            indexer.setIndexFormatVersion(indexFormatVersion);
            indexer.setMaxExtractLength(getMaxExtractLength());
            Document doc = indexer.createDoc();
            mergeAggregatedNodeIndexes(node, doc, indexFormatVersion);
            return doc;
        }
    
        /**
         * Returns the actual index.
         *
         * @return the actual index.
         */
        protected MultiIndex getIndex() {
            return index;
        }
    
        /**
         * @return the sort comparator source for this index.
         */
        protected SortComparatorSource getSortComparatorSource() {
            return scs;
        }
    
        /**
         * @param namespaceMappings The namespace mappings
         * @return the fulltext indexing configuration or <code>null</code> if there
         *         is no configuration.
         */
        protected IndexingConfiguration createIndexingConfiguration(NamespaceMappings namespaceMappings) {
            Element docElement = getIndexingConfigurationDOM();
            if (docElement == null) {
                return null;
            }
            try {
                IndexingConfiguration idxCfg = (IndexingConfiguration)
                        indexingConfigurationClass.newInstance();
                idxCfg.init(docElement, getContext(), namespaceMappings);
                return idxCfg;
            } catch (Exception e) {
                log.warn("Exception initializing indexing configuration from: "
                        + indexingConfigPath, e);
            }
            log.warn(indexingConfigPath + " ignored.");
            return null;
        }
    
        /**
         * @return the configured synonym provider or <code>null</code> if none is
         *         configured or an error occurs.
         */
        protected SynonymProvider createSynonymProvider() {
            SynonymProvider sp = null;
            if (synonymProviderClass != null) {
                try {
                    sp = (SynonymProvider) synonymProviderClass.newInstance();
                    sp.initialize(createSynonymProviderConfigResource());
                } catch (Exception e) {
                    log.warn("Exception initializing synonym provider: "
                            + synonymProviderClass, e);
                    sp = null;
                }
            }
            return sp;
        }
    
        /**
         * @return an initialized {@link DirectoryManager}.
         * @throws IOException if the directory manager cannot be instantiated or
         *          an exception occurs while initializing the manager.
         */
        protected DirectoryManager createDirectoryManager()
                throws IOException {
            try {
                Class<?> clazz = Class.forName(directoryManagerClass);
                if (!DirectoryManager.class.isAssignableFrom(clazz)) {
                    throw new IOException(directoryManagerClass +
                            " is not a DirectoryManager implementation");
                }
                DirectoryManager df = (DirectoryManager) clazz.newInstance();
                df.init(this);
                return df;
            } catch (IOException e) {
                throw e;
            } catch (Exception e) {
                IOException ex = new IOException();
                ex.initCause(e);
                throw ex;
            }
        }
    
        /**
         * Creates a redo log factory based on {@link #getRedoLogFactoryClass()}.
         *
         * @return the redo log factory.
         * @throws IOException if an error occurs while creating the factory.
         */
        protected RedoLogFactory createRedoLogFactory() throws IOException {
            try {
                Class<?> clazz = Class.forName(redoLogFactoryClass);
                if (!RedoLogFactory.class.isAssignableFrom(clazz)) {
                    throw new IOException(redoLogFactoryClass +
                            " is not a RedoLogFactory implementation");
                }
                return (RedoLogFactory) clazz.newInstance();
            } catch (Exception e) {
                IOException ex = new IOException();
                ex.initCause(e);
                throw ex;
            }
        }
    
        /**
         * Creates a file system resource to the synonym provider configuration.
         *
         * @return a file system resource or <code>null</code> if no path was
         *         configured.
         * @throws FileSystemException if an exception occurs accessing the file
         *                             system.
         * @throws IOException         if another exception occurs.
         */
        protected FileSystemResource createSynonymProviderConfigResource()
                throws FileSystemException, IOException {
            if (synonymProviderConfigPath != null) {
                FileSystemResource fsr;
                // simple sanity check
                if (synonymProviderConfigPath.endsWith(FileSystem.SEPARATOR)) {
                    throw new FileSystemException(
                            "Invalid synonymProviderConfigPath: "
                            + synonymProviderConfigPath);
                }
                if (fs == null) {
                    fs = new LocalFileSystem();
                    int lastSeparator = synonymProviderConfigPath.lastIndexOf(
                            FileSystem.SEPARATOR_CHAR);
                    if (lastSeparator != -1) {
                        File root = new File(path,
                                synonymProviderConfigPath.substring(0, lastSeparator));
                        ((LocalFileSystem) fs).setRoot(root.getCanonicalFile());
                        fs.init();
                        fsr = new FileSystemResource(fs,
                                synonymProviderConfigPath.substring(lastSeparator + 1));
                    } else {
                        ((LocalFileSystem) fs).setPath(path);
                        fs.init();
                        fsr = new FileSystemResource(fs, synonymProviderConfigPath);
                    }
                    synonymProviderConfigFs = fs;
                } else {
                    fsr = new FileSystemResource(fs, synonymProviderConfigPath);
                }
                return fsr;
            } else {
                // path not configured
                return null;
            }
        }
    
        /**
         * Creates a spell checker for this query handler.
         *
         * @return the spell checker or <code>null</code> if none is configured or
         *         an error occurs.
         */
        protected SpellChecker createSpellChecker() {
            SpellChecker spCheck = null;
            if (spellCheckerClass != null) {
                try {
                    spCheck = (SpellChecker) spellCheckerClass.newInstance();
                    spCheck.init(this);
                } catch (Exception e) {
                    log.warn("Exception initializing spell checker: "
                            + spellCheckerClass, e);
                }
            }
            return spCheck;
        }
    
        /**
         * Returns the document element of the indexing configuration or
         * <code>null</code> if there is no indexing configuration.
         *
         * @return the indexing configuration or <code>null</code> if there is
         *         none.
         */
        protected Element getIndexingConfigurationDOM() {
            if (indexingConfiguration != null) {
                return indexingConfiguration;
            }
            if (indexingConfigPath == null) {
                return null;
            }
            File config = new File(indexingConfigPath);
            InputStream configStream = null;
    
            if (!config.exists()) {
                // check if it's a classpath resource
                configStream = getClass().getResourceAsStream(indexingConfigPath);
    
                if (configStream == null) {
                    // only warn if not available also in the classpath
                    log.warn("File does not exist: " + indexingConfigPath);
                    return null;
                }
            } else if (!config.canRead()) {
                log.warn("Cannot read file: " + indexingConfigPath);
                return null;
            }
            try {
                DocumentBuilderFactory factory =
                        DocumentBuilderFactory.newInstance();
                DocumentBuilder builder = factory.newDocumentBuilder();
                builder.setEntityResolver(new IndexingConfigurationEntityResolver());
    
                if (configStream != null) {
                    indexingConfiguration = builder
                        .parse(configStream).getDocumentElement();
                } else {
                    indexingConfiguration = builder
                        .parse(config).getDocumentElement();
                }
            } catch (ParserConfigurationException e) {
                log.warn("Unable to create XML parser", e);
            } catch (IOException e) {
                log.warn("Exception parsing " + indexingConfigPath, e);
            } catch (SAXException e) {
                log.warn("Exception parsing " + indexingConfigPath, e);
            } finally {
                if (configStream != null) {
                    try {
                        configStream.close();
                    } catch (IOException e) {
                        // ignore
                    }
                }
            }
            return indexingConfiguration;
        }
    
        /**
         * Merges the fulltext indexed fields of the aggregated node states into
         * <code>doc</code>.
         *
         * @param state the node state on which <code>doc</code> was created.
         * @param doc the lucene document with index fields from <code>state</code>.
         * @param ifv the current index format version.
         */
        protected void mergeAggregatedNodeIndexes(NodeState state,
                                                  Document doc,
                                                  IndexFormatVersion ifv) {
            if (indexingConfig != null) {
                AggregateRule[] aggregateRules = indexingConfig.getAggregateRules();
                if (aggregateRules == null) {
                    return;
                }
                try {
                    ItemStateManager ism = getContext().getItemStateManager();
                    for (AggregateRule aggregateRule : aggregateRules) {
                        boolean ruleMatched = false;
                        // node includes
                        NodeState[] aggregates = aggregateRule.getAggregatedNodeStates(state);
                        if (aggregates != null) {
                            ruleMatched = true;
                            for (NodeState aggregate : aggregates) {
                                Document aDoc = createDocument(aggregate, getNamespaceMappings(), ifv);
                                // transfer fields to doc if there are any
                                Fieldable[] fulltextFields = aDoc.getFieldables(FieldNames.FULLTEXT);
                                if (fulltextFields != null) {
                                    for (Fieldable fulltextField : fulltextFields) {
                                        doc.add(fulltextField);
                                    }
                                    doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID, aggregate.getNodeId().toString(), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
                                }
                            }
                            // make sure that fulltext fields are aligned properly
                            // first all stored fields, then remaining
                            List<Fieldable> fulltextFields = new ArrayList<Fieldable>();
                            fulltextFields.addAll(removeFields(doc, FieldNames.FULLTEXT));
                            Collections.sort(fulltextFields, new Comparator<Fieldable>() {
                                public int compare(Fieldable o1, Fieldable o2) {
                                    return Boolean.valueOf(o2.isStored()).compareTo(o1.isStored());
                                }
                            });
                            for (Fieldable f : fulltextFields) {
                                doc.add(f);
                            }
                        }
                        // property includes
                        PropertyState[] propStates = aggregateRule.getAggregatedPropertyStates(state);
                        if (propStates != null) {
                            ruleMatched = true;
                            for (PropertyState propState : propStates) {
                                String namePrefix = FieldNames.createNamedValue(getNamespaceMappings().translateName(propState.getName()), "");
                                NodeState parent = (NodeState) ism.getItemState(propState.getParentId());
                                Document aDoc = createDocument(parent, getNamespaceMappings(), ifv);
                                try {
                                    // find the right fields to transfer
                                    Fieldable[] fields = aDoc.getFieldables(FieldNames.PROPERTIES);
                                    Token t = new Token();
                                    for (Fieldable field : fields) {
                                        // assume properties fields use SingleTokenStream
                                        t = field.tokenStreamValue().next(t);
                                        String value = new String(t.termBuffer(), 0, t.termLength());
                                        if (value.startsWith(namePrefix)) {
                                            // extract value
                                            value = value.substring(namePrefix.length());
                                            // create new named value
                                            Path p = getRelativePath(state, propState);
                                            String path = getNamespaceMappings().translatePath(p);
                                            value = FieldNames.createNamedValue(path, value);
                                            t.setTermBuffer(value);
                                            doc.add(new Field(field.name(), new SingletonTokenStream(t)));
                                            doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID, parent.getNodeId().toString(), Field.Store.NO, Field.Index.NOT_ANALYZED_NO_NORMS));
                                        }
                                    }
                                } finally {
                                    Util.disposeDocument(aDoc);
                                }
                            }
                        }
    
                        // only use first aggregate definition that matches
                        if (ruleMatched) {
                            break;
                        }
                    }
                } catch (Exception e) {
                    // do not fail if aggregate cannot be created
                    log.warn("Exception while building indexing aggregate for"
                            + " node with id: " + state.getNodeId(), e);
                }
            }
        }
    
        /**
         * Removes the fields with the given <code>name</code> from the
         * <code>document</code> and returns them in a collection.
         *
         * @param document the document.
         * @param name     the name of the fields to remove.
         * @return the removed fields.
         */
        protected final Collection<Fieldable> removeFields(Document document,
                                                     String name) {
            List<Fieldable> fields = new ArrayList<Fieldable>();
            fields.addAll(Arrays.asList(document.getFieldables(name)));
            document.removeFields(FieldNames.FULLTEXT);
            return fields;
        }
    
        /**
         * Returns the relative path from <code>nodeState</code> to
         * <code>propState</code>.
         *
         * @param nodeState a node state.
         * @param propState a property state.
         * @return the relative path.
         * @throws RepositoryException if an error occurs while resolving paths.
         * @throws ItemStateException  if an error occurs while reading item
         *                             states.
         */
        protected Path getRelativePath(NodeState nodeState, PropertyState propState)
                throws RepositoryException, ItemStateException {
            HierarchyManager hmgr = getContext().getHierarchyManager();
            Path nodePath = hmgr.getPath(nodeState.getId());
            Path propPath = hmgr.getPath(propState.getId());
            Path p = nodePath.computeRelativePath(propPath);
            // make sure it does not contain indexes
            boolean clean = true;
            Path.Element[] elements = p.getElements();
            for (int i = 0; i < elements.length; i++) {
                if (elements[i].getIndex() != 0) {
                    elements[i] = PATH_FACTORY.createElement(elements[i].getName());
                    clean = false;
                }
            }
            if (!clean) {
                p = PATH_FACTORY.create(elements);
            }
            return p;
        }
    
        /**
         * Retrieves the root of the indexing aggregate for <code>state</code> and
         * puts it into <code>map</code>.
         *
         * @param state the node state for which we want to retrieve the aggregate
         *              root.
         * @param map   aggregate roots are collected in this map.
         */
        protected void retrieveAggregateRoot(
                NodeState state, Map<NodeId, NodeState> map) {
            if (indexingConfig != null) {
                AggregateRule[] aggregateRules = indexingConfig.getAggregateRules();
                if (aggregateRules == null) {
                    return;
                }
                try {
                    for (AggregateRule aggregateRule : aggregateRules) {
                        NodeState root = aggregateRule.getAggregateRoot(state);
                        if (root != null) {
                            map.put(root.getNodeId(), root);
                        }
                    }
                } catch (Exception e) {
                    log.warn("Unable to get aggregate root for "
                            + state.getNodeId(), e);
                }
            }
        }
    
        /**
         * Retrieves the root of the indexing aggregate for <code>removedIds</code>
         * and puts it into <code>map</code>.
         *
         * @param removedIds     the ids of removed nodes.
         * @param map            aggregate roots are collected in this map
         */
        protected void retrieveAggregateRoot(
                Set<NodeId> removedIds, Map<NodeId, NodeState> map) {
            if (indexingConfig != null) {
                AggregateRule[] aggregateRules = indexingConfig.getAggregateRules();
                if (aggregateRules == null) {
                    return;
                }
                int found = 0;
                long time = System.currentTimeMillis();
                try {
                    CachingMultiIndexReader reader = index.getIndexReader();
                    try {
                        Term aggregateIds =
                            new Term(FieldNames.AGGREGATED_NODE_UUID, "");
                        TermDocs tDocs = reader.termDocs();
                        try {
                            ItemStateManager ism = getContext().getItemStateManager();
                            for (NodeId id : removedIds) {
                                aggregateIds =
                                    aggregateIds.createTerm(id.toString());
                                tDocs.seek(aggregateIds);
                                while (tDocs.next()) {
                                    Document doc = reader.document(
                                            tDocs.doc(), FieldSelectors.UUID);
                                    NodeId nId = new NodeId(doc.get(FieldNames.UUID));
                                    map.put(nId, (NodeState) ism.getItemState(nId));
                                    found++;
                                }
                            }
                        } finally {
                            tDocs.close();
                        }
                    } finally {
                        reader.release();
                    }
                } catch (Exception e) {
                    log.warn("Exception while retrieving aggregate roots", e);
                }
                time = System.currentTimeMillis() - time;
                log.debug("Retrieved {} aggregate roots in {} ms.", found, time);
            }
        }
    
        //----------------------------< internal >----------------------------------
    
        /**
         * Combines multiple {@link CachingMultiIndexReader} into a <code>MultiReader</code>
         * with {@link HierarchyResolver} support.
         */
        protected static final class CombinedIndexReader
                extends MultiReader
                implements HierarchyResolver, MultiIndexReader {
    
            /**
             * The sub readers.
             */
            private final CachingMultiIndexReader[] subReaders;
    
            /**
             * Doc number starts for each sub reader
             */
            private int[] starts;
    
            public CombinedIndexReader(CachingMultiIndexReader[] indexReaders) {
                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[] getParents(int n, int[] docNumbers) throws IOException {
                int i = readerIndex(n);
                DocId id = subReaders[i].getParentDocId(n - starts[i]);
                id = id.applyOffset(starts[i]);
                return id.getDocumentNumbers(this, docNumbers);
            }
    
            //-------------------------< MultiIndexReader >-------------------------
    
            /**
             * {@inheritDoc}
             */
            public IndexReader[] getIndexReaders() {
                IndexReader[] readers = new IndexReader[subReaders.length];
                System.arraycopy(subReaders, 0, readers, 0, subReaders.length);
                return readers;
            }
    
            /**
             * {@inheritDoc}
             */
            public void release() throws IOException {
                for (CachingMultiIndexReader subReader : subReaders) {
                    subReader.release();
                }
            }
    
            //---------------------------< internal >-------------------------------
    
            /**
             * Returns the reader index for document <code>n</code>.
             * Implementation copied from lucene MultiReader class.
             *
             * @param n document number.
             * @return the reader index.
             */
            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;
            }
    
            public boolean equals(Object obj) {
                if (obj instanceof CombinedIndexReader) {
                    CombinedIndexReader other = (CombinedIndexReader) obj;
                    return Arrays.equals(subReaders, other.subReaders);
                }
                return false;
            }
    
            public int hashCode() {
                int hash = 0;
                for (CachingMultiIndexReader subReader : subReaders) {
                    hash = 31 * hash + subReader.hashCode();
                }
                return hash;
            }
    
            /**
             * {@inheritDoc}
             */
            public ForeignSegmentDocId createDocId(NodeId id) throws IOException {
                for (CachingMultiIndexReader subReader : subReaders) {
                    ForeignSegmentDocId doc = subReader.createDocId(id);
                    if (doc != null) {
                        return doc;
                    }
                }
                return null;
            }
    
            /**
             * {@inheritDoc}
             */
            public int getDocumentNumber(ForeignSegmentDocId docId) {
                for (int i = 0; i < subReaders.length; i++) {
                    CachingMultiIndexReader subReader = subReaders[i];
                    int realDoc = subReader.getDocumentNumber(docId);
                    if (realDoc >= 0) {
                        return realDoc + starts[i];
                    }
                }
                return -1;
            }
        }
    
        //--------------------------< properties >----------------------------------
    
        /**
         * Sets the analyzer in use for indexing. The given analyzer class name
         * must satisfy the following conditions:
         * <ul>
         *   <li>the class must exist in the class path</li>
         *   <li>the class must have a public default constructor</li>
         *   <li>the class must be a Lucene Analyzer</li>
         * </ul>
         * <p>
         * If the above conditions are met, then a new instance of the class is
         * set as the analyzer. Otherwise a warning is logged and the current
         * analyzer is not changed.
         * <p>
         * This property setter method is normally invoked by the Jackrabbit
         * configuration mechanism if the "analyzer" parameter is set in the
         * search configuration.
         *
         * @param analyzerClassName the analyzer class name
         */
        public void setAnalyzer(String analyzerClassName) {
            try {
                Class<?> analyzerClass = Class.forName(analyzerClassName);
                analyzer.setDefaultAnalyzer((Analyzer) analyzerClass.newInstance());
            } catch (Exception e) {
                log.warn("Invalid Analyzer class: " + analyzerClassName, e);
            }
        }
    
        /**
         * Returns the class name of the analyzer that is currently in use.
         *
         * @return class name of analyzer in use.
         */
        public String getAnalyzer() {
            return analyzer.getClass().getName();
        }
    
        /**
         * Sets the location of the search index.
         *
         * @param path the location of the search index.
         */
        public void setPath(String path) {
            this.path = path;
        }
    
        /**
         * Returns the location of the search index. Returns <code>null</code> if
         * not set.
         *
         * @return the location of the search index.
         */
        public String getPath() {
            return path;
        }
    
        /**
         * The lucene index writer property: useCompoundFile
         */
        public void setUseCompoundFile(boolean b) {
            useCompoundFile = b;
        }
    
        /**
         * Returns the current value for useCompoundFile.
         *
         * @return the current value for useCompoundFile.
         */
        public boolean getUseCompoundFile() {
            return useCompoundFile;
        }
    
        /**
         * The lucene index writer property: minMergeDocs
         */
        public void setMinMergeDocs(int minMergeDocs) {
            this.minMergeDocs = minMergeDocs;
        }
    
        /**
         * Returns the current value for minMergeDocs.
         *
         * @return the current value for minMergeDocs.
         */
        public int getMinMergeDocs() {
            return minMergeDocs;
        }
    
        /**
         * Sets the property: volatileIdleTime
         *
         * @param volatileIdleTime idle time in seconds
         */
        public void setVolatileIdleTime(int volatileIdleTime) {
            this.volatileIdleTime = volatileIdleTime;
        }
    
        /**
         * Returns the current value for volatileIdleTime.
         *
         * @return the current value for volatileIdleTime.
         */
        public int getVolatileIdleTime() {
            return volatileIdleTime;
        }
    
        /**
         * The lucene index writer property: maxMergeDocs
         */
        public void setMaxMergeDocs(int maxMergeDocs) {
            this.maxMergeDocs = maxMergeDocs;
        }
    
        /**
         * Returns the current value for maxMergeDocs.
         *
         * @return the current value for maxMergeDocs.
         */
        public int getMaxMergeDocs() {
            return maxMergeDocs;
        }
    
        /**
         * The lucene index writer property: mergeFactor
         */
        public void setMergeFactor(int mergeFactor) {
            this.mergeFactor = mergeFactor;
        }
    
        /**
         * Returns the current value for the merge factor.
         *
         * @return the current value for the merge factor.
         */
        public int getMergeFactor() {
            return mergeFactor;
        }
    
        /**
         * @see VolatileIndex#setBufferSize(int)
         */
        public void setBufferSize(int size) {
            bufferSize = size;
        }
    
        /**
         * Returns the current value for the buffer size.
         *
         * @return the current value for the buffer size.
         */
        public int getBufferSize() {
            return bufferSize;
        }
    
        public void setRespectDocumentOrder(boolean docOrder) {
            documentOrder = docOrder;
        }
    
        public boolean getRespectDocumentOrder() {
            return documentOrder;
        }
    
        public void setForceConsistencyCheck(boolean b) {
            forceConsistencyCheck = b;
        }
    
        public boolean getForceConsistencyCheck() {
            return forceConsistencyCheck;
        }
    
        public void setAutoRepair(boolean b) {
            autoRepair = b;
        }
    
        public boolean getAutoRepair() {
            return autoRepair;
        }
    
        public void setCacheSize(int size) {
            cacheSize = size;
        }
    
        public int getCacheSize() {
            return cacheSize;
        }
    
        public void setMaxFieldLength(int length) {
            maxFieldLength = length;
        }
    
        public int getMaxFieldLength() {
            return maxFieldLength;
        }
    
        public void setMaxExtractLength(int length) {
            maxExtractLength = length;
        }
    
        public int getMaxExtractLength() {
            if (maxExtractLength < 0) {
                return -maxExtractLength * maxFieldLength;
            } else {
                return maxExtractLength;
            }
        }
    
        /**
         * Sets the list of text extractors (and text filters) to use for
         * extracting text content from binary properties. The list must be
         * comma (or whitespace) separated, and contain fully qualified class
         * names of the {@link TextExtractor} (and {@link org.apache.jackrabbit.core.query.TextFilter}) classes
         * to be used. The configured classes must all have a public default
         * constructor.
         *
         * @param filterClasses comma separated list of class names
         * @deprecated 
         */
        public void setTextFilterClasses(String filterClasses) {
            parser.setTextFilterClasses(filterClasses);
        }
    
        /**
         * Returns the fully qualified class names of the text filter instances
         * currently in use. The names are comma separated.
         *
         * @return class names of the text filters in use.
         * @deprecated 
         */
        public String getTextFilterClasses() {
            return "deprectated";
        }
    
        /**
         * Tells the query handler how many result should be fetched initially when
         * a query is executed.
         *
         * @param size the number of results to fetch initially.
         */
        public void setResultFetchSize(int size) {
            resultFetchSize = size;
        }
    
        /**
         * @return the number of results the query handler will fetch initially when
         *         a query is executed.
         */
        public int getResultFetchSize() {
            return resultFetchSize;
        }
    
        /**
         * The number of background threads for the extractor pool.
         *
         * @param numThreads the number of threads.
         */
        public void setExtractorPoolSize(int numThreads) {
            if (numThreads < 0) {
                numThreads = 0;
            }
            extractorPoolSize = numThreads;
        }
    
        /**
         * @return the size of the thread pool which is used to run the text
         *         extractors when binary content is indexed.
         */
        public int getExtractorPoolSize() {
            return extractorPoolSize;
        }
    
        /**
         * The number of extractor jobs that are queued until a new job is executed
         * with the current thread instead of using the thread pool.
         *
         * @param backLog size of the extractor job queue.
         */
        public void setExtractorBackLogSize(int backLog) {
            extractorBackLog = backLog;
        }
    
        /**
         * @return the size of the extractor queue back log.
         */
        public int getExtractorBackLogSize() {
            return extractorBackLog;
        }
    
        /**
         * The timeout in milliseconds which is granted to the text extraction
         * process until fulltext indexing is deferred to a background thread.
         *
         * @param timeout the timeout in milliseconds.
         */
        public void setExtractorTimeout(long timeout) {
            extractorTimeout = timeout;
        }
    
        /**
         * @return the extractor timeout in milliseconds.
         */
        public long getExtractorTimeout() {
            return extractorTimeout;
        }
    
        /**
         * If set to <code>true</code> additional information is stored in the index
         * to support highlighting using the rep:excerpt pseudo property.
         *
         * @param b <code>true</code> to enable highlighting support.
         */
        public void setSupportHighlighting(boolean b) {
            supportHighlighting = b;
        }
    
        /**
         * @return <code>true</code> if highlighting support is enabled.
         */
        public boolean getSupportHighlighting() {
            return supportHighlighting;
        }
    
        /**
         * Sets the class name for the {@link ExcerptProvider} that should be used
         * for the rep:excerpt pseudo property in a query.
         *
         * @param className the name of a class that implements {@link
         *                  ExcerptProvider}.
         */
        public void setExcerptProviderClass(String className) {
            try {
                Class<?> clazz = Class.forName(className);
                if (ExcerptProvider.class.isAssignableFrom(clazz)) {
                    excerptProviderClass = clazz;
                } else {
                    log.warn("Invalid value for excerptProviderClass, {} does "
                            + "not implement ExcerptProvider interface.", className);
                }
            } catch (ClassNotFoundException e) {
                log.warn("Invalid value for excerptProviderClass, class {} not found.",
                        className);
            }
        }
    
        /**
         * @return the class name of the excerpt provider implementation.
         */
        public String getExcerptProviderClass() {
            return excerptProviderClass.getName();
        }
    
        /**
         * Sets the path to the indexing configuration file.
         *
         * @param path the path to the configuration file.
         */
        public void setIndexingConfiguration(String path) {
            indexingConfigPath = path;
        }
    
        /**
         * @return the path to the indexing configuration file.
         */
        public String getIndexingConfiguration() {
            return indexingConfigPath;
        }
    
        /**
         * Sets the name of the class that implements {@link IndexingConfiguration}.
         * The default value is <code>org.apache.jackrabbit.core.query.lucene.IndexingConfigurationImpl</code>.
         *
         * @param className the name of the class that implements {@link
         *                  IndexingConfiguration}.
         */
        public void setIndexingConfigurationClass(String className) {
            try {
                Class<?> clazz = Class.forName(className);
                if (IndexingConfiguration.class.isAssignableFrom(clazz)) {
                    indexingConfigurationClass = clazz;
                } else {
                    log.warn("Invalid value for indexingConfigurationClass, {} "
                            + "does not implement IndexingConfiguration interface.",
                            className);
                }
            } catch (ClassNotFoundException e) {
                log.warn("Invalid value for indexingConfigurationClass, class {} not found.",
                        className);
            }
        }
    
        /**
         * @return the class name of the indexing configuration implementation.
         */
        public String getIndexingConfigurationClass() {
            return indexingConfigurationClass.getName();
        }
    
        /**
         * Sets the name of the class that implements {@link SynonymProvider}. The
         * default value is <code>null</code> (none set).
         *
         * @param className name of the class that implements {@link
         *                  SynonymProvider}.
         */
        public void setSynonymProviderClass(String className) {
            try {
                Class<?> clazz = Class.forName(className);
                if (SynonymProvider.class.isAssignableFrom(clazz)) {
                    synonymProviderClass = clazz;
                } else {
                    log.warn("Invalid value for synonymProviderClass, {} "
                            + "does not implement SynonymProvider interface.",
                            className);
                }
            } catch (ClassNotFoundException e) {
                log.warn("Invalid value for synonymProviderClass, class {} not found.",
                        className);
            }
        }
    
        /**
         * @return the class name of the synonym provider implementation or
         *         <code>null</code> if none is set.
         */
        public String getSynonymProviderClass() {
            if (synonymProviderClass != null) {
                return synonymProviderClass.getName();
            } else {
                return null;
            }
        }
    
        /**
         * Sets the name of the class that implements {@link SpellChecker}. The
         * default value is <code>null</code> (none set).
         *
         * @param className name of the class that implements {@link SpellChecker}.
         */
        public void setSpellCheckerClass(String className) {
            try {
                Class<?> clazz = Class.forName(className);
                if (SpellChecker.class.isAssignableFrom(clazz)) {
                    spellCheckerClass = clazz;
                } else {
                    log.warn("Invalid value for spellCheckerClass, {} "
                            + "does not implement SpellChecker interface.",
                            className);
                }
            } catch (ClassNotFoundException e) {
                log.warn("Invalid value for spellCheckerClass,"
                        + " class {} not found.", className);
            }
        }
    
        /**
         * @return the class name of the spell checker implementation or
         *         <code>null</code> if none is set.
         */
        public String getSpellCheckerClass() {
            if (spellCheckerClass != null) {
                return spellCheckerClass.getName();
            } else {
                return null;
            }
        }
    
        /**
         * Enables or disables the consistency check on startup. Consistency checks
         * are disabled per default.
         *
         * @param b <code>true</code> enables consistency checks.
         * @see #setForceConsistencyCheck(boolean)
         */
        public void setEnableConsistencyCheck(boolean b) {
            this.consistencyCheckEnabled = b;
        }
    
        /**
         * @return <code>true</code> if consistency checks are enabled.
         */
        public boolean getEnableConsistencyCheck() {
            return consistencyCheckEnabled;
        }
    
        /**
         * Sets the configuration path for the synonym provider.
         *
         * @param path the configuration path for the synonym provider.
         */
        public void setSynonymProviderConfigPath(String path) {
            synonymProviderConfigPath = path;
        }
    
        /**
         * @return the configuration path for the synonym provider. If none is set
         *         this method returns <code>null</code>.
         */
        public String getSynonymProviderConfigPath() {
            return synonymProviderConfigPath;
        }
    
        /**
         * Sets the similarity implementation, which will be used for indexing and
         * searching. The implementation must extend {@link Similarity}.
         *
         * @param className a {@link Similarity} implementation.
         */
        public void setSimilarityClass(String className) {
            try {
                Class<?> similarityClass = Class.forName(className);
                similarity = (Similarity) similarityClass.newInstance();
            } catch (Exception e) {
                log.warn("Invalid Similarity class: " + className, e);
            }
        }
    
        /**
         * @return the name of the similarity class.
         */
        public String getSimilarityClass() {
            return similarity.getClass().getName();
        }
    
        /**
         * Sets a new maxVolatileIndexSize value.
         *
         * @param maxVolatileIndexSize the new value.
         */
        public void setMaxVolatileIndexSize(long maxVolatileIndexSize) {
            this.maxVolatileIndexSize = maxVolatileIndexSize;
        }
    
        /**
         * @return the maxVolatileIndexSize in bytes.
         */
        public long getMaxVolatileIndexSize() {
            return maxVolatileIndexSize;
        }
    
        /**
         * @return the name of the directory manager class.
         */
        public String getDirectoryManagerClass() {
            return directoryManagerClass;
        }
    
        /**
         * Sets name of the directory manager class. The class must implement
         * {@link DirectoryManager}.
         *
         * @param className the name of the class that implements directory manager.
         */
        public void setDirectoryManagerClass(String className) {
            this.directoryManagerClass = className;
        }
    
        /**
         * @return the current value for termInfosIndexDivisor.
         */
        public int getTermInfosIndexDivisor() {
            return termInfosIndexDivisor;
        }
    
        /**
         * Sets a new value for termInfosIndexDivisor.
         *
         * @param termInfosIndexDivisor the new value.
         */
        public void setTermInfosIndexDivisor(int termInfosIndexDivisor) {
            this.termInfosIndexDivisor = termInfosIndexDivisor;
        }
    
        /**
         * @return <code>true</code> if the hierarchy cache should be initialized
         *         immediately on startup.
         */
        public boolean isInitializeHierarchyCache() {
            return initializeHierarchyCache;
        }
    
        /**
         * Whether the hierarchy cache should be initialized immediately on
         * startup.
         *
         * @param initializeHierarchyCache <code>true</code> if the cache should be
         *                                 initialized immediately.
         */
        public void setInitializeHierarchyCache(boolean initializeHierarchyCache) {
            this.initializeHierarchyCache = initializeHierarchyCache;
        }
    
        /**
         * @return the maximum age in seconds for outdated generations of
         * {@link IndexInfos}.
         */
        public long getMaxHistoryAge() {
            return maxHistoryAge;
        }
    
        /**
         * Sets a new value for the maximum age in seconds for outdated generations
         * of {@link IndexInfos}.
         *
         * @param maxHistoryAge age in seconds.
         */
        public void setMaxHistoryAge(long maxHistoryAge) {
            this.maxHistoryAge = maxHistoryAge;
        }
    
        /**
         * @return the name of the redo log factory class.
         */
        public String getRedoLogFactoryClass() {
            return redoLogFactoryClass;
        }
    
        /**
         * Sets the name of the redo log factory class. Must implement
         * {@link RedoLogFactory}.
         *
         * @param className the name of the redo log factory class.
         */
        public void setRedoLogFactoryClass(String className) {
            this.redoLogFactoryClass = className;
        }
    
        //----------------------------< internal >----------------------------------
    
        /**
         * Checks if this <code>SearchIndex</code> is open, otherwise throws
         * an <code>IOException</code>.
         *
         * @throws IOException if this <code>SearchIndex</code> had been closed.
         */
        protected void checkOpen() throws IOException {
            if (closed) {
                throw new IOException("query handler closed and cannot be used anymore.");
            }
        }
    }

    SearchIndex类的源码实在过长,其实是里面大部分均为与lucene索引与检索的相关参数及执行环境等

    先看它的初始化方法(有抽象父类构造方法调用)

    /**
         * Initializes this <code>QueryHandler</code>. This implementation requires
         * that a path parameter is set in the configuration. If this condition
         * is not met, a <code>IOException</code> is thrown.
         *
         * @throws IOException if an error occurs while initializing this handler.
         */
        protected void doInit() throws IOException {
            QueryHandlerContext context = getContext();
            if (path == null) {
                throw new IOException("SearchIndex requires 'path' parameter in configuration!");
            }
    
            Set<NodeId> excludedIDs = new HashSet<NodeId>();
            if (context.getExcludedNodeId() != null) {
                excludedIDs.add(context.getExcludedNodeId());
            }
    
            synProvider = createSynonymProvider();
            directoryManager = createDirectoryManager();
            redoLogFactory = createRedoLogFactory();
    
            if (context.getParentHandler() instanceof SearchIndex) {
                // use system namespace mappings
                SearchIndex sysIndex = (SearchIndex) context.getParentHandler();
                nsMappings = sysIndex.getNamespaceMappings();
            } else {
                // read local namespace mappings
                File mapFile = new File(new File(path), NS_MAPPING_FILE);
                if (mapFile.exists()) {
                    // be backward compatible and use ns_mappings.properties from
                    // index folder
                    nsMappings = new FileBasedNamespaceMappings(mapFile);
                } else {
                    // otherwise use repository wide stable index prefix from
                    // namespace registry
                    nsMappings = new NSRegistryBasedNamespaceMappings(
                            context.getNamespaceRegistry());
                }
            }
    
            scs = new SharedFieldSortComparator(
                    FieldNames.PROPERTIES, context.getItemStateManager(),
                    context.getHierarchyManager(), nsMappings);
            indexingConfig = createIndexingConfiguration(nsMappings);
            analyzer.setIndexingConfig(indexingConfig);
    
            index = new MultiIndex(this, excludedIDs);
            if (index.numDocs() == 0) {
                Path rootPath;
                if (excludedIDs.isEmpty()) {
                    // this is the index for jcr:system
                    rootPath = JCR_SYSTEM_PATH;
                } else {
                    rootPath = ROOT_PATH;
                }
                index.createInitialIndex(context.getItemStateManager(),
                        context.getRootId(), rootPath);
            }
            if (consistencyCheckEnabled
                    && (index.getRedoLogApplied() || forceConsistencyCheck)) {
                log.info("Running consistency check...");
                try {
                    ConsistencyCheck check = runConsistencyCheck();
                    if (autoRepair) {
                        check.repair(true);
                    } else {
                        List<ConsistencyCheckError> errors = check.getErrors();
                        if (errors.size() == 0) {
                            log.info("No errors detected.");
                        }
                        for (ConsistencyCheckError err : errors) {
                            log.info(err.toString());
                        }
                    }
                } catch (Exception e) {
                    log.warn("Failed to run consistency check on index: " + e);
                }
            }
    
            // initialize spell checker
            spellChecker = createSpellChecker();
    
            log.info("Index initialized: {} Version: {}",
                    new Object[]{path, index.getIndexFormatVersion()});
            if (!index.getIndexFormatVersion().equals(getIndexFormatVersion())) {
                log.warn("Using Version {} for reading. Please re-index version " +
                        "storage for optimal performance.",
                        getIndexFormatVersion().getVersion());
            }
        }

    主要是初始化必要的成员变量,如lucene的路径,索引执行类,分词器,解析器,日志类等

    它的索引操作方法如下:

    /**
         * This implementation forwards the call to
         * {@link MultiIndex#update(Collection, Collection)} and
         * transforms the two iterators to the required types.
         *
         * @param remove ids of nodes to remove.
         * @param add    NodeStates to add. Calls to <code>next()</code> on this
         *               iterator may return <code>null</code>, to indicate that a
         *               node could not be indexed successfully.
         * @throws RepositoryException if an error occurs while indexing a node.
         * @throws IOException         if an error occurs while updating the index.
         */
        public void updateNodes(Iterator<NodeId> remove, Iterator<NodeState> add)
                throws RepositoryException, IOException {
            checkOpen();
    
            Map<NodeId, NodeState> aggregateRoots = new HashMap<NodeId, NodeState>();
            Set<NodeId> removedIds = new HashSet<NodeId>();
            Set<NodeId> addedIds = new HashSet<NodeId>();
    
            Collection<NodeId> removeCollection = new ArrayList<NodeId>();
            while (remove.hasNext()) {
                NodeId id = remove.next();
                removeCollection.add(id);
                removedIds.add(id);
            }
    
            Collection<Document> addCollection = new ArrayList<Document>();
            while (add.hasNext()) {
                NodeState state = add.next();
                if (state != null) {
                    NodeId id = state.getNodeId();
                    addedIds.add(id);
                    removedIds.remove(id);
                    retrieveAggregateRoot(state, aggregateRoots);
    
                    try {
                        addCollection.add(createDocument(
                                state, getNamespaceMappings(),
                                index.getIndexFormatVersion()));
                    } catch (RepositoryException e) {
                        log.warn("Exception while creating document for node: "
                                + state.getNodeId() + ": " + e.toString());
                    }
                }
            }
    
            index.update(removeCollection, addCollection);
    
            // remove any aggregateRoot nodes that are new
            // and therefore already up-to-date
            aggregateRoots.keySet().removeAll(addedIds);
    
            // based on removed ids get affected aggregate root nodes
            retrieveAggregateRoot(removedIds, aggregateRoots);
    
            // update aggregates if there are any affected
            if (!aggregateRoots.isEmpty()) {
                Collection<Document> modified =
                    new ArrayList<Document>(aggregateRoots.size());
    
                for (NodeState state : aggregateRoots.values()) {
                    try {
                        modified.add(createDocument(
                                state, getNamespaceMappings(),
                                index.getIndexFormatVersion()));
                    } catch (RepositoryException e) {
                        log.warn("Exception while creating document for node: "
                                + state.getNodeId(), e);
                    }
                }
    
                index.update(aggregateRoots.keySet(), modified);
            }
        }

    这里实现的主要是删除索引和添加索引,最后调用index的update方法(MultiIndex类型的index)

    添加索引部分调用了自身的createDocument方法创建Document

    /**
         * Creates a lucene <code>Document</code> for a node state using the
         * namespace mappings <code>nsMappings</code>.
         *
         * @param node               the node state to index.
         * @param nsMappings         the namespace mappings of the search index.
         * @param indexFormatVersion the index format version that should be used to
         *                           index the passed node state.
         * @return a lucene <code>Document</code> that contains all properties of
         *         <code>node</code>.
         * @throws RepositoryException if an error occurs while indexing the
         *                             <code>node</code>.
         */
        protected Document createDocument(NodeState node,
                                          NamespaceMappings nsMappings,
                                          IndexFormatVersion indexFormatVersion)
                throws RepositoryException {
            NodeIndexer indexer = new NodeIndexer(
                    node, getContext().getItemStateManager(), nsMappings,
                    getContext().getExecutor(), parser);
            indexer.setSupportHighlighting(supportHighlighting);
            indexer.setIndexingConfiguration(indexingConfig);
            indexer.setIndexFormatVersion(indexFormatVersion);
            indexer.setMaxExtractLength(getMaxExtractLength());
            Document doc = indexer.createDoc();
            mergeAggregatedNodeIndexes(node, doc, indexFormatVersion);
            return doc;
        }

    这里显然是调用NodeIndexer类来创建Document的,上文已经涉及了

    其次是lucene的检索方法

    /**
         * Creates a new query by specifying the query statement itself and the
         * language in which the query is stated.  If the query statement is
         * syntactically invalid, given the language specified, an
         * InvalidQueryException is thrown. <code>language</code> must specify a query language
         * string from among those returned by QueryManager.getSupportedQueryLanguages(); if it is not
         * then an <code>InvalidQueryException</code> is thrown.
         *
         * @param sessionContext component context of the current session
         * @param statement the query statement.
         * @param language the syntax of the query statement.
         * @throws InvalidQueryException if statement is invalid or language is unsupported.
         * @return A <code>Query</code> object.
         */
        public ExecutableQuery createExecutableQuery(
                SessionContext sessionContext, String statement, String language)
                throws InvalidQueryException {
            QueryImpl query = new QueryImpl(
                    sessionContext, this, getContext().getPropertyTypeRegistry(),
                    statement, language, getQueryNodeFactory());
            query.setRespectDocumentOrder(documentOrder);
            return query;
        }

    这里是通过封装org.apache.jackrabbit.core.query.lucene.QueryImpl类来实现lucene的检索功能的,等后文再分析吧

    SearchIndex类的其他功能方法尚待发掘中,本文到此打住吧 

    ---------------------------------------------------------------------------

    本系列Apache Jackrabbit源码研究系本人原创

    转载请注明出处 博客园 刺猬的温驯

    本文链接 http://www.cnblogs.com/chenying99/archive/2013/04/07/3003296.html

  • 相关阅读:
    实验 1:Mininet 源码安装和可视化拓扑工具
    Linux bond 网卡主备模式配置
    git常用命令总结
    框架搭建之--配置去除用户输入两边的空格
    性能优化之--数据库缓存
    性能优化之缓存--文件缓存
    网站优化之缓存--页面缓存
    网站优化之缓存--session和cache以及常用缓存应用之间的区别
    MVC请求管道详细总结梳理。
    sql优化记录
  • 原文地址:https://www.cnblogs.com/chenying99/p/3003296.html
Copyright © 2020-2023  润新知