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

svn commit: r530696 [1/2] - /jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/

Author: mreutegg
Date: Fri Apr 20 01:46:06 2007
New Revision: 530696

URL: http://svn.apache.org/viewvc?view=rev&rev=530696
Log:
JCR-202: Add configuration options for search manager

Added:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfiguration.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java   (with props)
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/indexing-configuration-1.0.dtd   (with props)
Modified:
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
    jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java?view=auto&rev=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRule.java Fri Apr 20 01:46:06 2007
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.ItemStateException;
+
+import javax.jcr.RepositoryException;
+
+/**
+ * <code>AggregateRule</code> defines a configuration for a node index
+ * aggregate. It defines rules for items that should be included in the node
+ * scope index of an ancestor. Per default the values of properties are only
+ * added to the node scope index of the parent node.
+ */
+public interface AggregateRule {
+
+    /**
+     * Returns root node state for the indexing aggregate where
+     * <code>nodeState</code> belongs to.
+     *
+     * @param nodeState
+     * @return the root node state of the indexing aggregate or
+     *         <code>null</code> if <code>nodeState</code> does not belong to an
+     *         indexing aggregate.
+     * @throws ItemStateException  if an error occurs.
+     * @throws RepositoryException if an error occurs.
+     */
+    NodeState getAggregateRoot(NodeState nodeState)
+            throws ItemStateException, RepositoryException;
+
+    /**
+     * Returns the node states that are part of the indexing aggregate of the
+     * <code>nodeState</code>.
+     *
+     * @param nodeState a node state
+     * @return the node states that are part of the indexing aggregate of
+     *         <code>nodeState</code>. Returns <code>null</code> if this
+     *         aggregate does not apply to <code>nodeState</code>.
+     * @throws ItemStateException if an error occurs.
+     */
+    NodeState[] getAggregatedNodeStates(NodeState nodeState)
+            throws ItemStateException;
+}

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

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java?view=auto&rev=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/AggregateRuleImpl.java Fri Apr 20 01:46:06 2007
@@ -0,0 +1,347 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.apache.jackrabbit.name.Path;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.IllegalNameException;
+import org.apache.jackrabbit.name.UnknownPrefixException;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.NodeId;
+import org.apache.jackrabbit.util.Text;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.CharacterData;
+
+import javax.jcr.RepositoryException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Arrays;
+import java.util.Iterator;
+
+/**
+ * <code>AggregateRule</code> defines a configuration for a node index
+ * aggregate. It defines rules for items that should be included in the node
+ * scope index of an ancestor. Per default the values of properties are only
+ * added to the node scope index of the parent node.
+ */
+class AggregateRuleImpl implements AggregateRule {
+
+    /**
+     * A namespace resolver for parsing QNames in the configuration.
+     */
+    private final NamespaceResolver nsResolver;
+
+    /**
+     * The node type of the root node of the indexing aggregate.
+     */
+    private final QName nodeTypeName;
+
+    /**
+     * The rules that define this indexing aggregate.
+     */
+    private final Rule[] rules;
+
+    /**
+     * The item state manager to retrieve additional item states.
+     */
+    private final ItemStateManager ism;
+
+    /**
+     * A hierarchy resolver for the item state manager.
+     */
+    private final HierarchyManager hmgr;
+
+    /**
+     * Creates a new indexing aggregate using the given <code>config</code>.
+     *
+     * @param config     the configuration for this indexing aggregate.
+     * @param nsResolver the namespace resolver for parsing QNames within the
+     *                   config.
+     * @param ism        the item state manager of the workspace.
+     * @param hmgr       a hierarchy manager for the item state manager.
+     * @throws MalformedPathException if a path in the configuration is
+     *                                malformed.
+     * @throws IllegalNameException   if a node type name contains illegal
+     *                                characters.
+     * @throws UnknownPrefixException if a node type contains an unknown
+     *                                prefix.
+     */
+    AggregateRuleImpl(Node config,
+                      NamespaceResolver nsResolver,
+                      ItemStateManager ism,
+                      HierarchyManager hmgr)
+            throws MalformedPathException, IllegalNameException, UnknownPrefixException {
+        this.nsResolver = nsResolver;
+        this.nodeTypeName = getNodeTypeName(config);
+        this.rules = getRules(config);
+        this.ism = ism;
+        this.hmgr = hmgr;
+    }
+
+    /**
+     * Returns root node state for the indexing aggregate where
+     * <code>nodeState</code> belongs to.
+     *
+     * @param nodeState
+     * @return the root node state of the indexing aggregate or
+     *         <code>null</code> if <code>nodeState</code> does not belong to an
+     *         indexing aggregate.
+     * @throws ItemStateException  if an error occurs.
+     * @throws RepositoryException if an error occurs.
+     */
+    public NodeState getAggregateRoot(NodeState nodeState)
+            throws ItemStateException, RepositoryException {
+        for (int i = 0; i < rules.length; i++) {
+            NodeState aggregateRoot = rules[i].matches(nodeState);
+            if (aggregateRoot != null &&
+                    aggregateRoot.getNodeTypeName().equals(nodeTypeName)) {
+                return aggregateRoot;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Returns the node states that are part of the indexing aggregate of the
+     * <code>nodeState</code>.
+     *
+     * @param nodeState a node state
+     * @return the node states that are part of the indexing aggregate of
+     *         <code>nodeState</code>. Returns <code>null</code> if this
+     *         aggregate does not apply to <code>nodeState</code>.
+     * @throws ItemStateException  if an error occurs.
+     */
+    public NodeState[] getAggregatedNodeStates(NodeState nodeState)
+            throws ItemStateException {
+        if (nodeState.getNodeTypeName().equals(nodeTypeName)) {
+            List nodeStates = new ArrayList();
+            for (int i = 0; i < rules.length; i++) {
+                nodeStates.addAll(Arrays.asList(rules[i].resolve(nodeState)));
+            }
+            if (nodeStates.size() > 0) {
+                return (NodeState[]) nodeStates.toArray(new NodeState[nodeStates.size()]);
+            }
+        }
+        return null;
+    }
+
+    //---------------------------< internal >-----------------------------------
+
+    /**
+     * Reads the node type of the root node of the indexing aggregate.
+     *
+     * @param config the configuration.
+     * @return the name of the node type.
+     * @throws IllegalNameException   if the node type name contains illegal
+     *                                characters.
+     * @throws UnknownPrefixException if the node type contains an unknown
+     *                                prefix.
+     */
+    private QName getNodeTypeName(Node config)
+            throws IllegalNameException, UnknownPrefixException {
+        String ntString = config.getAttributes().getNamedItem("primaryType").getNodeValue();
+        return NameFormat.parse(ntString, nsResolver);
+    }
+
+    /**
+     * Creates rules defined in the <code>config</code>.
+     *
+     * @param config the indexing aggregate configuration.
+     * @return the rules defined in the <code>config</code>.
+     * @throws MalformedPathException if a path in the configuration is
+     *                                malformed.
+     * @throws IllegalNameException   if the node type name contains illegal
+     *                                characters.
+     * @throws UnknownPrefixException if the node type contains an unknown
+     *                                prefix.
+     */
+    private Rule[] getRules(Node config)
+            throws MalformedPathException, IllegalNameException, UnknownPrefixException {
+        List rules = new ArrayList();
+        NodeList childNodes = config.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node n = childNodes.item(i);
+            if (n.getNodeName().equals("include")) {
+                QName ntName = null;
+                Node ntAttr = n.getAttributes().getNamedItem("primaryType");
+                if (ntAttr != null) {
+                    ntName = NameFormat.parse(ntAttr.getNodeValue(), nsResolver);
+                }
+                String[] elements = Text.explode(getTextContent(n), '/');
+                Path.PathBuilder builder = new Path.PathBuilder();
+                for (int j = 0; j < elements.length; j++) {
+                    if (elements[j].equals("*")) {
+                        builder.addLast(new QName("", "*"));
+                    } else {
+                        builder.addLast(NameFormat.parse(elements[j], nsResolver));
+                    }
+                }
+                rules.add(new Rule(builder.getPath(), ntName));
+            }
+        }
+        return (Rule[]) rules.toArray(new Rule[rules.size()]);
+    }
+
+    //---------------------------< internal >-----------------------------------
+
+    /**
+     * @param node a node.
+     * @return the text content of the <code>node</code>.
+     */
+    private static String getTextContent(Node node) {
+        StringBuffer content = new StringBuffer();
+        NodeList nodes = node.getChildNodes();
+        for (int i = 0; i < nodes.getLength(); i++) {
+            Node n = nodes.item(i);
+            if (n.getNodeType() == Node.TEXT_NODE) {
+                content.append(((CharacterData) n).getData());
+            }
+        }
+        return content.toString();
+    }
+
+    private final class Rule {
+
+        /**
+         * Optional node type name.
+         */
+        private final QName nodeTypeName;
+
+        /**
+         * A relative path pattern.
+         */
+        private final Path pattern;
+
+        /**
+         * Creates a new rule with a relative path pattern and an optional node
+         * type name.
+         *
+         * @param nodeTypeName node type name or <code>null</code> if all node
+         *                     types are allowed.
+         * @param pattern      a relative path pattern.
+         */
+        private Rule(Path pattern, QName nodeTypeName) {
+            this.nodeTypeName = nodeTypeName;
+            this.pattern = pattern;
+        }
+
+        /**
+         * If the given <code>nodeState</code> matches this rule the root node
+         * state of the indexing aggregate is returned.
+         *
+         * @param nodeState a node state.
+         * @return the root node state of the indexing aggregate or
+         *         <code>null</code> if <code>nodeState</code> does not belong
+         *         to an indexing aggregate defined by this rule.
+         */
+        NodeState matches(NodeState nodeState)
+                throws ItemStateException, RepositoryException {
+            // first check node type
+            if (nodeTypeName == null ||
+                    nodeState.getNodeTypeName().equals(nodeTypeName)) {
+                // check pattern
+                Path.PathElement[] elements = pattern.getElements();
+                for (int e = elements.length - 1; e >= 0; e--) {
+                    NodeId parentId = nodeState.getParentId();
+                    if (parentId == null) {
+                        // nodeState is root node
+                        return null;
+                    }
+                    NodeState parent = (NodeState) ism.getItemState(parentId);
+                    if (elements[e].getName().getLocalName().equals("*")) {
+                        // match any parent
+                        nodeState = parent;
+                    } else {
+                        // check name
+                        QName name = hmgr.getName(nodeState.getId());
+                        if (elements[e].getName().equals(name)) {
+                            nodeState = parent;
+                        } else {
+                            return null;
+                        }
+                    }
+                }
+                // if we get here nodeState became the root
+                // of the indexing aggregate and is valid
+                return nodeState;
+            }
+            return null;
+        }
+
+        /**
+         * Resolves the <code>nodeState</code> using this rule.
+         *
+         * @param nodeState the root node of the enclosing indexing aggregate.
+         * @return the descendant node states as defined by this rule.
+         * @throws ItemStateException if an error occurs while resolving the
+         *                            node states.
+         */
+        NodeState[] resolve(NodeState nodeState) throws ItemStateException {
+            List nodeStates = new ArrayList();
+            resolve(nodeState, nodeStates, 0);
+            return (NodeState[]) nodeStates.toArray(new NodeState[nodeStates.size()]);
+        }
+
+        //-----------------------------< internal >-----------------------------
+
+        /**
+         * Recursively resolves node states along the path {@link #pattern}.
+         *
+         * @param nodeState the current node state.
+         * @param collector resolved node states are collected using the list.
+         * @param offset    the current path element offset into the path
+         *                  pattern.
+         * @throws ItemStateException if an error occurs while accessing node
+         *                            states.
+         */
+        private void resolve(NodeState nodeState, List collector, int offset)
+                throws ItemStateException {
+            QName currentName = pattern.getElement(offset).getName();
+            List cne;
+            if (currentName.getLocalName().equals("*")) {
+                // matches all
+                cne = nodeState.getChildNodeEntries();
+            } else {
+                cne = nodeState.getChildNodeEntries(currentName);
+            }
+            if (pattern.getLength() - 1 == offset) {
+                // last segment -> add to collector if node type matches
+                for (Iterator it = cne.iterator(); it.hasNext(); ) {
+                    NodeId id = ((NodeState.ChildNodeEntry) it.next()).getId();
+                    NodeState ns = (NodeState) ism.getItemState(id);
+                    if (ns.getNodeTypeName().equals(nodeTypeName)) {
+                        collector.add(ns);
+                    }
+                }
+            } else {
+                // traverse
+                offset++;
+                for (Iterator it = cne.iterator(); it.hasNext(); ) {
+                    NodeId id = ((NodeState.ChildNodeEntry) it.next()).getId();
+                    resolve((NodeState) ism.getItemState(id), collector, offset);
+                }
+            }
+        }
+    }
+}

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

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java?view=diff&rev=530696&r1=530695&r2=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/FieldNames.java Fri Apr 20 01:46:06 2007
@@ -71,6 +71,12 @@
     public static final String PROPERTIES = "_:PROPERTIES".intern();
 
     /**
+     * Name of the field that contains the UUIDs of the aggregated nodes. The
+     * terms are not tokenized and not stored, only indexed.
+     */
+    public static final String AGGREGATED_NODE_UUID = "_:AGGR_NODE_UUID".intern();
+
+    /**
      * Returns a named value for use as a term in the index. The named
      * value is of the form: <code>fieldName</code> + '\uFFFF' + value
      *

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfiguration.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfiguration.java?view=auto&rev=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfiguration.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfiguration.java Fri Apr 20 01:46:06 2007
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.query.QueryHandlerContext;
+import org.apache.jackrabbit.name.QName;
+import org.w3c.dom.Element;
+
+/**
+ * <code>IndexingConfiguration</code> defines the interface to check whether
+ * a certain property is indexed or not. This interface also provides access
+ * to aggregate rules. Those define how node indexes are combined into an
+ * aggregate to form a single node index that can be queried.
+ */
+public interface IndexingConfiguration {
+
+    /**
+     * The default boost: 1.0f.
+     */
+    public static final float DEFAULT_BOOST = 1.0f;
+
+    /**
+     * Initializes the configuration.
+     *
+     * @param config the document element of the configuration DOM.
+     * @param context the context of the query handler.
+     * @throws Exception if initialization fails.
+     */
+    public void init(Element config, QueryHandlerContext context) throws Exception;
+
+    /**
+     * Returns the configured indexing aggregate rules or <code>null</code> if
+     * none exist. The caller must not modify the returned array!
+     *
+     * @return the configured rules or <code>null</code> if none exist.
+     */
+    public AggregateRule[] getAggregateRules();
+
+    /**
+     * Returns <code>true</code> if the property with the given name is indexed
+     * according to this configuration.
+     *
+     * @param state        the node state.
+     * @param propertyName the name of a property.
+     * @return <code>true</code> if the property is indexed; <code>false</code>
+     *         otherwise.
+     */
+    boolean isIndexed(NodeState state, QName propertyName);
+
+    /**
+     * Returns <code>true</code> if the property with the given name should be
+     * included in the node scope fulltext index. If there is not configuration
+     * entry for that propery <code>false</code> is returned.
+     *
+     * @param state the node state.
+     * @param propertyName the name of a property.
+     * @return <code>true</code> if the property should be included in the node
+     *         scope fulltext index.
+     */
+    boolean isIncludedInNodeScopeIndex(NodeState state, QName propertyName);
+
+    /**
+     * Returns the boost value for the given property name. If there is no
+     * configuration entry for the property name the {@link #DEFAULT_BOOST} is
+     * returned.
+     *
+     * @param state        the node state.
+     * @param propertyName the name of a property.
+     * @return the boost value for the property.
+     */
+    float getPropertyBoost(NodeState state, QName propertyName);
+
+    /**
+     * Returns the boost for the node scope fulltext index field.
+     *
+     * @param state the node state.
+     * @return the boost for the node scope fulltext index field.
+     */
+    float getNodeBoost(NodeState state);
+}

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

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java?view=auto&rev=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationEntityResolver.java Fri Apr 20 01:46:06 2007
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * <code>IndexingConfigurationEntityResolver</code> implements an entity
+ * resolver for the indexing configuration DTD.
+ */
+public class IndexingConfigurationEntityResolver implements EntityResolver {
+
+    /**
+     * The system id of the indexing configuration DTD.
+     */
+    private static final String SYSTEM_ID =
+            "http://jackrabbit.apache.org/dtd/indexing-configuration-1.0.dtd";
+
+    /**
+     * The name of the DTD resource.
+     */
+    private static final String RESOURCE_NAME = "indexing-configuration-1.0.dtd";
+
+    /**
+     * {@inheritDoc}
+     */
+    public InputSource resolveEntity(String publicId, String systemId)
+            throws SAXException, IOException {
+        if (SYSTEM_ID.equals(systemId)) {
+            InputStream in = getClass().getResourceAsStream(RESOURCE_NAME);
+            if (in != null) {
+                return new InputSource(in);
+            }
+        }
+        return null;
+    }
+}

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

Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java?view=auto&rev=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/IndexingConfigurationImpl.java Fri Apr 20 01:46:06 2007
@@ -0,0 +1,735 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.jackrabbit.core.query.lucene;
+
+import org.apache.jackrabbit.name.NamespaceResolver;
+import org.apache.jackrabbit.name.UnknownPrefixException;
+import org.apache.jackrabbit.name.IllegalNameException;
+import org.apache.jackrabbit.name.MalformedPathException;
+import org.apache.jackrabbit.name.QName;
+import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.name.NameResolver;
+import org.apache.jackrabbit.name.ParsingNameResolver;
+import org.apache.jackrabbit.name.PathResolver;
+import org.apache.jackrabbit.name.ParsingPathResolver;
+import org.apache.jackrabbit.core.state.ItemStateManager;
+import org.apache.jackrabbit.core.state.NodeState;
+import org.apache.jackrabbit.core.state.ItemStateException;
+import org.apache.jackrabbit.core.state.PropertyState;
+import org.apache.jackrabbit.core.HierarchyManager;
+import org.apache.jackrabbit.core.PropertyId;
+import org.apache.jackrabbit.core.HierarchyManagerImpl;
+import org.apache.jackrabbit.core.nodetype.xml.AdditionalNamespaceResolver;
+import org.apache.jackrabbit.core.nodetype.NodeTypeRegistry;
+import org.apache.jackrabbit.core.query.QueryHandlerContext;
+import org.apache.jackrabbit.core.value.InternalValue;
+import org.apache.jackrabbit.util.ISO9075;
+import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
+import org.w3c.dom.CharacterData;
+import org.w3c.dom.Node;
+import org.w3c.dom.NodeList;
+import org.w3c.dom.NamedNodeMap;
+import org.w3c.dom.Attr;
+import org.w3c.dom.Element;
+
+import javax.jcr.RepositoryException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.NoSuchElementException;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Properties;
+
+/**
+ * <code>IndexingConfigurationImpl</code> implements a concrete indexing
+ * configuration.
+ */
+public class IndexingConfigurationImpl implements IndexingConfiguration {
+
+    /**
+     * A namespace resolver for parsing QNames in the configuration.
+     */
+    private NamespaceResolver nsResolver;
+
+    /**
+     * The node type registry.
+     */
+    private NodeTypeRegistry ntReg;
+
+    /**
+     * The item state manager to retrieve additional item states.
+     */
+    private ItemStateManager ism;
+
+    /**
+     * A hierarchy resolver for the item state manager.
+     */
+    private HierarchyManager hmgr;
+
+    /**
+     * The {@link IndexingRule}s inside this configuration.
+     */
+    private Map configElements = new HashMap();
+
+    /**
+     * The indexing aggregates inside this configuration.
+     */
+    private AggregateRule[] aggregateRules;
+
+    /**
+     * {@inheritDoc}
+     */
+    public void init(Element config, QueryHandlerContext context) throws Exception {
+        ntReg = context.getNodeTypeRegistry();
+        ism = context.getItemStateManager();
+        NameResolver nameResolver = new ParsingNameResolver(
+                context.getNamespaceRegistry());
+        PathResolver pathResolver = new ParsingPathResolver(nameResolver);
+        hmgr = new HierarchyManagerImpl(context.getRootId(), ism, pathResolver);
+        nsResolver = new AdditionalNamespaceResolver(getNamespaces(config));
+
+        QName[] ntNames = ntReg.getRegisteredNodeTypes();
+        List idxAggregates = new ArrayList();
+        NodeList indexingConfigs = config.getChildNodes();
+        for (int i = 0; i < indexingConfigs.getLength(); i++) {
+            Node configNode = indexingConfigs.item(i);
+            if (configNode.getNodeName().equals("index-rule")) {
+                IndexingRule element = new IndexingRule(configNode);
+                // register under node type and all its sub types
+                for (int n = 0; n < ntNames.length; n++) {
+                    if (ntReg.getEffectiveNodeType(ntNames[n]).includesNodeType(
+                            element.getNodeTypeName())) {
+                        List perNtConfig = (List) configElements.get(ntNames[n]);
+                        if (perNtConfig == null) {
+                            perNtConfig = new ArrayList();
+                            configElements.put(ntNames[n], perNtConfig);
+                        }
+                        perNtConfig.add(element);
+                    }
+                }
+            } else if (configNode.getNodeName().equals("aggregate")) {
+                idxAggregates.add(new AggregateRuleImpl(
+                        configNode, nsResolver, ism, hmgr));
+            }
+        }
+        aggregateRules = (AggregateRule[]) idxAggregates.toArray(
+                new AggregateRule[idxAggregates.size()]);
+    }
+
+    /**
+     * Returns the configured indexing aggregate rules or <code>null</code> if
+     * none exist.
+     *
+     * @return the configured rules or <code>null</code> if none exist.
+     */
+    public AggregateRule[] getAggregateRules() {
+        return aggregateRules;
+    }
+
+    /**
+     * Returns <code>true</code> if the property with the given name is fulltext
+     * indexed according to this configuration.
+     *
+     * @param state        the node state.
+     * @param propertyName the name of a property.
+     * @return <code>true</code> if the property is fulltext indexed;
+     *         <code>false</code> otherwise.
+     */
+    public boolean isIndexed(NodeState state, QName propertyName) {
+        IndexingRule rule = getApplicableIndexingRule(state);
+        if (rule != null) {
+            return rule.isIndexed(propertyName);
+        }
+        // none of the configs matches -> index property
+        return true;
+    }
+
+    /**
+     * Returns the boost value for the given property name. If there is no
+     * configuration entry for the property name the {@link #DEFAULT_BOOST} is
+     * returned.
+     *
+     * @param state        the node state.
+     * @param propertyName the name of a property.
+     * @return the boost value for the property.
+     */
+    public float getPropertyBoost(NodeState state, QName propertyName) {
+        IndexingRule rule = getApplicableIndexingRule(state);
+        if (rule != null) {
+            return rule.getBoost(propertyName);
+        }
+        return DEFAULT_BOOST;
+    }
+
+    /**
+     * Returns the boost for the node scope fulltext index field.
+     *
+     * @param state the node state.
+     * @return the boost for the node scope fulltext index field.
+     */
+    public float getNodeBoost(NodeState state) {
+        IndexingRule rule = getApplicableIndexingRule(state);
+        if (rule != null) {
+            return rule.getNodeBoost();
+        }
+        return DEFAULT_BOOST;
+    }
+
+    /**
+     * Returns <code>true</code> if the property with the given name should be
+     * included in the node scope fulltext index. If there is not configuration
+     * entry for that propery <code>false</code> is returned.
+     *
+     * @param state the node state.
+     * @param propertyName the name of a property.
+     * @return <code>true</code> if the property should be included in the node
+     *         scope fulltext index.
+     */
+    public boolean isIncludedInNodeScopeIndex(NodeState state,
+                                              QName propertyName) {
+        IndexingRule rule = getApplicableIndexingRule(state);
+        if (rule != null) {
+            return rule.isIncludedInNodeScopeIndex(propertyName);
+        }
+        // none of the config elements matched -> default is to include
+        return true;
+    }
+
+    //---------------------------------< internal >-----------------------------
+
+    /**
+     * Returns the first indexing rule that applies to the given node
+     * <code>state</code>.
+     *
+     * @param state a node state.
+     * @return the indexing rule or <code>null</code> if none applies.
+     */
+    private IndexingRule getApplicableIndexingRule(NodeState state) {
+        List rules = null;
+        List r = (List) configElements.get(state.getNodeTypeName());
+        if (r != null) {
+            rules = new ArrayList();
+            rules.addAll(r);
+        }
+
+        for (Iterator it = state.getMixinTypeNames().iterator(); it.hasNext(); ) {
+            r = (List) configElements.get(it.next());
+            if (r != null) {
+                if (rules == null) {
+                    rules = new ArrayList();
+                }
+                rules.addAll(r);
+            }
+        }
+
+        if (rules != null) {
+            for (Iterator it = rules.iterator(); it.hasNext(); ) {
+                IndexingRule ir = (IndexingRule) it.next();
+                if (ir.appliesTo(state)) {
+                    return ir;
+                }
+            }
+        }
+
+        // no applicable rule
+        return null;
+    }
+
+    /**
+     * Returns the namespaces declared on the <code>node</code>.
+     *
+     * @param node a DOM node.
+     * @return the namespaces
+     */
+    private Properties getNamespaces(Node node) {
+        Properties namespaces = new Properties();
+        NamedNodeMap attributes = node.getAttributes();
+        for (int i = 0; i < attributes.getLength(); i++) {
+            Attr attribute = (Attr) attributes.item(i);
+            if (attribute.getName().startsWith("xmlns:")) {
+                namespaces.setProperty(
+                        attribute.getName().substring(6), attribute.getValue());
+            }
+        }
+        return namespaces;
+    }
+
+    /**
+     * Creates property configurations defined in the <code>config</code>.
+     *
+     * @param config the fulltext indexing configuration.
+     * @return the property configurations defined in the <code>config</code>.
+     * @throws IllegalNameException   if the node type name contains illegal
+     *                                characters.
+     * @throws UnknownPrefixException if the node type contains an unknown
+     *                                prefix.
+     */
+    private Map getPropertyConfigs(Node config)
+            throws IllegalNameException, UnknownPrefixException {
+        Map configs = new HashMap();
+        NodeList childNodes = config.getChildNodes();
+        for (int i = 0; i < childNodes.getLength(); i++) {
+            Node n = childNodes.item(i);
+            if (n.getNodeName().equals("property")) {
+                NamedNodeMap attributes = n.getAttributes();
+                // get boost value
+                float boost = 1.0f;
+                Node boostAttr = attributes.getNamedItem("boost");
+                if (boostAttr != null) {
+                    try {
+                        boost = Float.parseFloat(boostAttr.getNodeValue());
+                    } catch (NumberFormatException e) {
+                        // use default
+                    }
+                }
+
+                // get nodeScopeIndex flag
+                boolean nodeScopeIndex = true;
+                Node nsIndex = attributes.getNamedItem("nodeScopeIndex");
+                if (nsIndex != null) {
+                    nodeScopeIndex = Boolean.valueOf(
+                            nsIndex.getNodeValue()).booleanValue();
+                }
+
+                // get property name
+                QName propName = NameFormat.parse(getTextContent(n), nsResolver);
+
+                configs.put(propName, new PropertyConfig(boost, nodeScopeIndex));
+            }
+        }
+        return configs;
+    }
+
+    /**
+     * Gets the condition expression from the configuration.
+     *
+     * @param config the config node.
+     * @return the condition expression or <code>null</code> if there is no
+     *         condition set on the <code>config</code>.
+     * @throws MalformedPathException if the condition string is malformed.
+     * @throws IllegalNameException   if a name contains illegal characters.
+     * @throws UnknownPrefixException if a name contains an unknown prefix.
+     */
+    private PathExpression getCondition(Node config)
+            throws MalformedPathException, IllegalNameException, UnknownPrefixException {
+        Node conditionAttr = config.getAttributes().getNamedItem("condition");
+        if (conditionAttr == null) {
+            return null;
+        }
+        String conditionString = conditionAttr.getNodeValue();
+        int idx;
+        int axis;
+        QName elementTest = null;
+        QName nameTest = null;
+        QName propertyName;
+        String propertyValue;
+
+        // parse axis
+        if (conditionString.startsWith("ancestor::")) {
+            axis = PathExpression.ANCESTOR;
+            idx = "ancestor::".length();
+        } else if (conditionString.startsWith("parent::")) {
+            axis = PathExpression.PARENT;
+            idx = "parent::".length();
+        } else if (conditionString.startsWith("@")) {
+            axis = PathExpression.SELF;
+            idx = "@".length();
+        } else {
+            axis = PathExpression.CHILD;
+            idx = 0;
+        }
+
+        try {
+            if (conditionString.startsWith("element(", idx)) {
+                int colon = conditionString.indexOf(',',
+                        idx + "element(".length());
+                String name = conditionString.substring(
+                        idx + "element(".length(), colon).trim();
+                if (!name.equals("*")) {
+                    nameTest = NameFormat.parse(ISO9075.decode(name), nsResolver);
+                }
+                idx = conditionString.indexOf(")/@", colon);
+                String type = conditionString.substring(colon + 1, idx).trim();
+                elementTest = NameFormat.parse(ISO9075.decode(type), nsResolver);
+                idx += ")/@".length();
+            } else {
+                if (axis == PathExpression.ANCESTOR ||
+                        axis == PathExpression.CHILD ||
+                        axis == PathExpression.PARENT) {
+                    // simple name test
+                    String name = conditionString.substring(idx,
+                            conditionString.indexOf('/', idx));
+                    if (!name.equals("*")) {
+                        nameTest = NameFormat.parse(ISO9075.decode(name), nsResolver);
+                    }
+                    idx += name.length() + "/@".length();
+                }
+            }
+
+            // parse property name
+            int eq = conditionString.indexOf('=', idx);
+            String name = conditionString.substring(idx, eq).trim();
+            propertyName = NameFormat.parse(ISO9075.decode(name), nsResolver);
+
+            // parse string value
+            int quote = conditionString.indexOf('\'', eq) + 1;
+            propertyValue = conditionString.substring(quote,
+                    conditionString.indexOf('\'', quote));
+        } catch (IndexOutOfBoundsException e) {
+            throw new MalformedPathException(conditionString);
+        }
+
+        return new PathExpression(axis, elementTest,
+                nameTest, propertyName, propertyValue);
+    }
+
+    /**
+     * @param node a node.
+     * @return the text content of the <code>node</code>.
+     */
+    private static String getTextContent(Node node) {
+        StringBuffer content = new StringBuffer();
+        NodeList nodes = node.getChildNodes();
+        for (int i = 0; i < nodes.getLength(); i++) {
+            Node n = nodes.item(i);
+            if (n.getNodeType() == Node.TEXT_NODE) {
+                content.append(((CharacterData) n).getData());
+            }
+        }
+        return content.toString();
+    }
+
+    private class IndexingRule {
+
+        /**
+         * The node type of this fulltext indexing rule.
+         */
+        private final QName nodeTypeName;
+
+        /**
+         * Map of {@link PropertyConfig}. Key=QName of property.
+         */
+        private final Map propConfigs;
+
+        /**
+         * An expression based on a relative path.
+         */
+        private final PathExpression condition;
+
+        /**
+         * The boost value for this config element.
+         */
+        private final float boost;
+
+        /**
+         *
+         * @param config the configuration for this rule.
+         * @throws MalformedPathException if the condition expression is malformed.
+         * @throws IllegalNameException   if a name contains illegal characters.
+         * @throws UnknownPrefixException if a name contains an unknown prefix.
+         */
+        IndexingRule(Node config)
+                throws MalformedPathException, IllegalNameException, UnknownPrefixException {
+            this.nodeTypeName = getNodeTypeName(config);
+            this.propConfigs = getPropertyConfigs(config);
+            this.condition = getCondition(config);
+            this.boost = getNodeBoost(config);
+        }
+
+        /**
+         * Returns the name of the node type where this rule applies to.
+         *
+         * @return name of the node type.
+         */
+        public QName getNodeTypeName() {
+            return nodeTypeName;
+        }
+
+        /**
+         * @return the value for the node boost.
+         */
+        public float getNodeBoost() {
+            return boost;
+        }
+
+        /**
+         * Returns <code>true</code> if the property with the given name is
+         * indexed according to this rule.
+         *
+         * @param propertyName the name of a property.
+         * @return <code>true</code> if the property is indexed;
+         *         <code>false</code> otherwise.
+         */
+        public boolean isIndexed(QName propertyName) {
+            return propConfigs.containsKey(propertyName);
+        }
+
+        /**
+         * Returns the boost value for the given property name. If there is no
+         * configuration entry for the property name the default boost value is
+         * returned.
+         *
+         * @param propertyName the name of a property.
+         * @return the boost value for the property.
+         */
+        public float getBoost(QName propertyName) {
+            PropertyConfig config = (PropertyConfig) propConfigs.get(propertyName);
+            if (config != null) {
+                return config.boost;
+            } else {
+                return DEFAULT_BOOST;
+            }
+        }
+
+        /**
+         * Returns <code>true</code> if the property with the given name should
+         * be included in the node scope fulltext index. If there is not
+         * configuration entry for that propery <code>false</code> is returned.
+         *
+         * @param propertyName the name of a property.
+         * @return <code>true</code> if the property should be included in the
+         *         node scope fulltext index.
+         */
+        public boolean isIncludedInNodeScopeIndex(QName propertyName) {
+            PropertyConfig config = (PropertyConfig) propConfigs.get(propertyName);
+            if (config != null) {
+                return config.nodeScopeIndex;
+            } else {
+                return false;
+            }
+        }
+
+        /**
+         * Returns <code>true</code> if this rule applies to the given node
+         * <code>state</code>.
+         *
+         * @param state the state to check.
+         * @return <code>true</code> the rule applies to the given node;
+         *         <code>false</code> otherwise.
+         */
+        public boolean appliesTo(NodeState state) {
+            if (!nodeTypeName.equals(state.getNodeTypeName())) {
+                return false;
+            }
+            if (condition == null) {
+                return true;
+            } else {
+                return condition.evaluate(state);
+            }
+        }
+
+        //-------------------------< internal >---------------------------------
+
+        /**
+         * Reads the node type of the root node of the indexing rule.
+         *
+         * @param config the configuration.
+         * @return the name of the node type.
+         * @throws IllegalNameException   if the node type name contains illegal
+         *                                characters.
+         * @throws UnknownPrefixException if the node type contains an unknown
+         *                                prefix.
+         */
+        private QName getNodeTypeName(Node config)
+                throws IllegalNameException, UnknownPrefixException {
+            String ntString = config.getAttributes().getNamedItem("nodeType").getNodeValue();
+            return NameFormat.parse(ntString, nsResolver);
+        }
+
+        /**
+         * Returns the node boost from the <code>config</code>.
+         *
+         * @param config the configuration.
+         * @return the configured node boost or the default boost if none is
+         *         configured.
+         */
+        private float getNodeBoost(Node config) {
+            Node boost = config.getAttributes().getNamedItem("boost");
+            if (boost != null) {
+                try {
+                    return Float.parseFloat(boost.getNodeValue());
+                } catch (NumberFormatException e) {
+                    // return default boost
+                }
+            }
+            return DEFAULT_BOOST;
+        }
+    }
+
+    /**
+     * Simple class that holds boost and nodeScopeIndex flag.
+     */
+    private class PropertyConfig {
+
+        /**
+         * The boost value for a property.
+         */
+        final float boost;
+
+        /**
+         * Flag that indicates whether a property is included in the node
+         * scope fulltext index of its parent.
+         */
+        final boolean nodeScopeIndex;
+
+        PropertyConfig(float boost, boolean nodeScopeIndex) {
+            this.boost = boost;
+            this.nodeScopeIndex = nodeScopeIndex;
+        }
+    }
+
+    private class PathExpression {
+
+        static final int SELF = 0;
+
+        static final int CHILD = 1;
+
+        static final int ANCESTOR = 2;
+
+        static final int PARENT = 3;
+
+        private final int axis;
+
+        private final QName elementTest;
+
+        private final QName nameTest;
+
+        private final QName propertyName;
+
+        private final String propertyValue;
+
+        PathExpression(int axis, QName elementTest, QName nameTest,
+                       QName propertyName, String propertyValue) {
+            this.axis = axis;
+            this.elementTest = elementTest;
+            this.nameTest = nameTest;
+            this.propertyName = propertyName;
+            this.propertyValue = propertyValue;
+        }
+
+        /**
+         * Evaluates this expression and returns <code>true</code> if the
+         * condition matches using <code>state</code> as the context node
+         * state.
+         *
+         * @param context the context from where the expression should be
+         *                evaluated.
+         * @return expression result.
+         */
+        boolean evaluate(final NodeState context) {
+            // get iterator along specified axis
+            Iterator nodeStates;
+            if (axis == SELF) {
+                nodeStates = Collections.singletonList(context).iterator();
+            } else if (axis == CHILD) {
+                nodeStates = new AbstractIteratorDecorator(
+                        context.getChildNodeEntries().iterator()) {
+                    public Object next() {
+                        NodeState.ChildNodeEntry cne =
+                                (NodeState.ChildNodeEntry) super.next();
+                        try {
+                            return (NodeState) ism.getItemState(cne.getId());
+                        } catch (ItemStateException e) {
+                            throw new NoSuchElementException();
+                        }
+                    }
+                };
+            } else if (axis == ANCESTOR) {
+                try {
+                    nodeStates = new Iterator() {
+
+                        private NodeState next =
+                                (NodeState) ism.getItemState(context.getParentId());
+
+                        public void remove() {
+                            throw new UnsupportedOperationException();
+                        }
+
+                        public boolean hasNext() {
+                            return next != null;
+                        }
+
+                        public Object next() {
+                            NodeState tmp = next;
+                            try {
+                                if (next.getParentId() != null) {
+                                    next = (NodeState) ism.getItemState(next.getParentId());
+                                } else {
+                                    next = null;
+                                }
+                            } catch (ItemStateException e) {
+                                next = null;
+                            }
+                            return tmp;
+                        }
+                    };
+                } catch (ItemStateException e) {
+                    nodeStates = Collections.EMPTY_LIST.iterator();
+                }
+            } else if (axis == PARENT) {
+                try {
+                    if (context.getParentId() != null) {
+                        NodeState state = (NodeState) ism.getItemState(context.getParentId());
+                        nodeStates = Collections.singletonList(state).iterator();
+                    } else {
+                        nodeStates = Collections.EMPTY_LIST.iterator();
+                    }
+                } catch (ItemStateException e) {
+                    nodeStates = Collections.EMPTY_LIST.iterator();
+                }
+            } else {
+                // unsupported axis
+                nodeStates = Collections.EMPTY_LIST.iterator();
+            }
+
+            // check node type, name and property value for each
+            while (nodeStates.hasNext()) {
+                try {
+                    NodeState current = (NodeState) nodeStates.next();
+                    if (elementTest != null &&
+                            !current.getNodeTypeName().equals(elementTest)) {
+                        continue;
+                    }
+                    if (nameTest != null &&
+                            !hmgr.getName(current.getNodeId()).equals(nameTest)) {
+                        continue;
+                    }
+                    if (!current.hasPropertyName(propertyName)) {
+                        continue;
+                    }
+                    PropertyId propId = new PropertyId(
+                            current.getNodeId(), propertyName);
+                    PropertyState propState =
+                            (PropertyState) ism.getItemState(propId);
+                    InternalValue[] values = propState.getValues();
+                    for (int i = 0; i < values.length; i++) {
+                        if (values[i].toString().equals(propertyValue)) {
+                            return true;
+                        }
+                    }
+                } catch (RepositoryException e) {
+                    // move on to next one
+                } catch (ItemStateException e) {
+                    // move on to next one
+                }
+            }
+            return false;
+        }
+    }
+}
\ No newline at end of file

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

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java?view=diff&rev=530696&r1=530695&r2=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/NodeIndexer.java Fri Apr 20 01:46:06 2007
@@ -58,6 +58,11 @@
     private static final Logger log = LoggerFactory.getLogger(NodeIndexer.class);
 
     /**
+     * The default boost for a lucene field: 1.0f.
+     */
+    protected static final float DEFAULT_BOOST = 1.0f;
+
+    /**
      * The <code>NodeState</code> of the node to index
      */
     protected final NodeState node;
@@ -79,6 +84,11 @@
     protected final TextExtractor extractor;
 
     /**
+     * The indexing configuration or <code>null</code> if none is available.
+     */
+    protected IndexingConfiguration indexingConfig;
+
+    /**
      * If set to <code>true</code> the fulltext field is stored and and a term
      * vector is created with offset information.
      */
@@ -93,9 +103,9 @@
      * @param extractor     content extractor
      */
     public NodeIndexer(NodeState node,
-                          ItemStateManager stateProvider,
-                          NamespaceMappings mappings,
-                          TextExtractor extractor) {
+                       ItemStateManager stateProvider,
+                       NamespaceMappings mappings,
+                       TextExtractor extractor) {
         this.node = node;
         this.stateProvider = stateProvider;
         this.mappings = mappings;
@@ -121,6 +131,15 @@
     }
 
     /**
+     * Sets the indexing configuration for this node indexer.
+     *
+     * @param config the indexing configuration.
+     */
+    public void setIndexingConfiguration(IndexingConfiguration config) {
+        this.indexingConfig = config;
+    }
+
+    /**
      * Creates a lucene Document.
      *
      * @return the lucene Document with the index layout.
@@ -130,6 +149,8 @@
     protected Document createDoc() throws RepositoryException {
         Document doc = new Document();
 
+        doc.setBoost(getNodeBoost());
+
         // special fields
         // UUID
         doc.add(new Field(FieldNames.UUID, node.getNodeId().getUUID().toString(), Field.Store.YES, Field.Index.UN_TOKENIZED, Field.TermVector.NO));
@@ -224,33 +245,61 @@
         Object internalValue = value.internalValue();
         switch (value.getType()) {
             case PropertyType.BINARY:
-                addBinaryValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addBinaryValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.BOOLEAN:
-                addBooleanValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addBooleanValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.DATE:
-                addCalendarValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addCalendarValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.DOUBLE:
-                addDoubleValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addDoubleValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.LONG:
-                addLongValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addLongValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.REFERENCE:
-                addReferenceValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addReferenceValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.PATH:
-                addPathValue(doc, fieldName, internalValue);
+                if (isIndexed(name)) {
+                    addPathValue(doc, fieldName, internalValue);
+                }
                 break;
             case PropertyType.STRING:
-                // do not fulltext index jcr:uuid String
-                boolean tokenize = !name.equals(QName.JCR_UUID);
-                addStringValue(doc, fieldName, internalValue, tokenize);
+                if (isIndexed(name)) {
+                    // never fulltext index jcr:uuid String
+                    if (name.equals(QName.JCR_UUID)) {
+                        addStringValue(doc, fieldName, internalValue,
+                                false, false, DEFAULT_BOOST);
+                    } else {
+                        addStringValue(doc, fieldName, internalValue,
+                                true, isIncludedInNodeIndex(name),
+                                getPropertyBoost(name));
+                    }
+                }
                 break;
             case PropertyType.NAME:
-                addNameValue(doc, fieldName, internalValue);
+                // jcr:primaryType and jcr:mixinTypes are required for correct
+                // node type resolution in queries
+                if (isIndexed(name) ||
+                        name.equals(QName.JCR_PRIMARYTYPE) ||
+                        name.equals(QName.JCR_MIXINTYPES)) {
+                    addNameValue(doc, fieldName, internalValue);
+                }
                 break;
             default:
                 throw new IllegalArgumentException("illegal internal value type");
@@ -450,7 +499,7 @@
      *             addStringValue(Document, String, Object, boolean)} instead.
      */
     protected void addStringValue(Document doc, String fieldName, Object internalValue) {
-        addStringValue(doc, fieldName, internalValue, true);
+        addStringValue(doc, fieldName, internalValue, true, true, DEFAULT_BOOST);
     }
 
     /**
@@ -464,7 +513,30 @@
      * @param tokenized     If <code>true</code> the string is also tokenized
      *                      and fulltext indexed.
      */
-    protected void addStringValue(Document doc, String fieldName, Object internalValue, boolean tokenized) {
+    protected void addStringValue(Document doc, String fieldName,
+                                  Object internalValue, boolean tokenized) {
+        addStringValue(doc, fieldName, internalValue, tokenized, true, DEFAULT_BOOST);
+    }
+
+    /**
+     * Adds the string value to the document both as the named field and
+     * optionally for full text indexing if <code>tokenized</code> is
+     * <code>true</code>.
+     *
+     * @param doc                The document to which to add the field
+     * @param fieldName          The name of the field to add
+     * @param internalValue      The value for the field to add to the
+     *                           document.
+     * @param tokenized          If <code>true</code> the string is also
+     *                           tokenized and fulltext indexed.
+     * @param includeInNodeIndex If <code>true</code> the string is also
+     *                           tokenized and added to the node scope fulltext
+     *                           index.
+     * @param boost              the boost value for this string field.
+     */
+    protected void addStringValue(Document doc, String fieldName,
+                                  Object internalValue, boolean tokenized,
+                                  boolean includeInNodeIndex, float boost) {
         String stringValue = String.valueOf(internalValue);
 
         // simple String
@@ -474,16 +546,21 @@
                 Field.Index.UN_TOKENIZED,
                 Field.TermVector.NO));
         if (tokenized) {
-            // also create fulltext index of this value
-            doc.add(createFulltextField(stringValue));
             // create fulltext index on property
             int idx = fieldName.indexOf(':');
             fieldName = fieldName.substring(0, idx + 1)
                     + FieldNames.FULLTEXT_PREFIX + fieldName.substring(idx + 1);
-            doc.add(new Field(fieldName, stringValue,
+            Field f = new Field(fieldName, stringValue,
                     Field.Store.NO,
                     Field.Index.TOKENIZED,
-                    Field.TermVector.NO));
+                    Field.TermVector.NO);
+            f.setBoost(boost);
+            doc.add(f);
+
+            if (includeInNodeIndex) {
+                // also create fulltext index of this value
+                doc.add(createFulltextField(stringValue));
+            }
         }
     }
 
@@ -566,6 +643,63 @@
             return createFulltextField(textExtract.toString());
         } else {
             return new Field(FieldNames.FULLTEXT, value);
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if the property with the given name should be
+     * indexed.
+     *
+     * @param propertyName name of a property.
+     * @return <code>true</code> if the property should be fulltext indexed;
+     *         <code>false</code> otherwise.
+     */
+    protected boolean isIndexed(QName propertyName) {
+        if (indexingConfig == null) {
+            return true;
+        } else {
+            return indexingConfig.isIndexed(node, propertyName);
+        }
+    }
+
+    /**
+     * Returns <code>true</code> if the property with the given name should also
+     * be added to the node scope index.
+     *
+     * @param propertyName the name of a property.
+     * @return <code>true</code> if it should be added to the node scope index;
+     *         <code>false</code> otherwise.
+     */
+    protected boolean isIncludedInNodeIndex(QName propertyName) {
+        if (indexingConfig == null) {
+            return true;
+        } else {
+            return indexingConfig.isIncludedInNodeScopeIndex(node, propertyName);
+        }
+    }
+
+    /**
+     * Returns the boost value for the given property name.
+     *
+     * @param propertyName the name of a property.
+     * @return the boost value for the given property name.
+     */
+    protected float getPropertyBoost(QName propertyName) {
+        if (indexingConfig == null) {
+            return DEFAULT_BOOST;
+        } else {
+            return indexingConfig.getPropertyBoost(node, propertyName);
+        }
+    }
+
+    /**
+     * @return the boost value for this {@link #node} state.
+     */
+    protected float getNodeBoost() {
+        if (indexingConfig == null) {
+            return DEFAULT_BOOST;
+        } else {
+            return indexingConfig.getNodeBoost(node);
         }
     }
 }

Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java?view=diff&rev=530696&r1=530695&r2=530696
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java Fri Apr 20 01:46:06 2007
@@ -26,17 +26,21 @@
 import org.apache.jackrabbit.core.query.QueryHandler;
 import org.apache.jackrabbit.core.state.NodeState;
 import org.apache.jackrabbit.core.state.NodeStateIterator;
+import org.apache.jackrabbit.core.state.ItemStateManager;
 import org.apache.jackrabbit.extractor.DefaultTextExtractor;
 import org.apache.jackrabbit.extractor.TextExtractor;
 import org.apache.jackrabbit.name.NoPrefixDeclaredException;
 import org.apache.jackrabbit.name.QName;
 import org.apache.jackrabbit.name.NameFormat;
+import org.apache.jackrabbit.uuid.UUID;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.apache.lucene.analysis.Analyzer;
 import org.apache.lucene.analysis.standard.StandardAnalyzer;
 import org.apache.lucene.index.IndexReader;
 import org.apache.lucene.index.MultiReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.index.TermDocs;
 import org.apache.lucene.search.Hits;
 import org.apache.lucene.search.IndexSearcher;
 import org.apache.lucene.search.Query;
@@ -45,9 +49,14 @@
 import org.apache.lucene.document.Document;
 import org.apache.lucene.document.Field;
 import org.apache.commons.collections.iterators.AbstractIteratorDecorator;
+import org.xml.sax.SAXException;
+import org.w3c.dom.Element;
 
 import javax.jcr.RepositoryException;
 import javax.jcr.query.InvalidQueryException;
+import javax.xml.parsers.DocumentBuilderFactory;
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.ParserConfigurationException;
 import java.io.IOException;
 import java.io.File;
 import java.util.Iterator;
@@ -56,6 +65,8 @@
 import java.util.HashSet;
 import java.util.Set;
 import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
 
 /**
  * Implements a {@link org.apache.jackrabbit.core.query.QueryHandler} using
@@ -132,6 +143,11 @@
     private TextExtractor extractor;
 
     /**
+     * The namespace mappings used internally.
+     */
+    private NamespaceMappings nsMappings;
+
+    /**
      * The location of the search index.
      * <p/>
      * Note: This is a <b>mandatory</b> parameter!
@@ -238,6 +254,28 @@
     private Class excerptProviderClass = DefaultXMLExcerpt.class;
 
     /**
+     * The path to the indexing configuration file.
+     */
+    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;
+
+    /**
      * Indicates if this <code>SearchIndex</code> is closed and cannot be used
      * anymore.
      */
@@ -269,10 +307,10 @@
         }
 
         extractor = createTextExtractor();
+        indexingConfig = createIndexingConfiguration();
 
         File indexDir = new File(path);
 
-        NamespaceMappings nsMappings;
         if (context.getParentHandler() instanceof SearchIndex) {
             // use system namespace mappings
             SearchIndex sysIndex = (SearchIndex) context.getParentHandler();
@@ -353,9 +391,14 @@
     public void updateNodes(NodeIdIterator remove, NodeStateIterator add)
             throws RepositoryException, IOException {
         checkOpen();
+        final Map aggregateRoots = new HashMap();
+        final Set removedNodeIds = new HashSet();
+        final Set addedNodeIds = new HashSet();
         index.update(new AbstractIteratorDecorator(remove) {
             public Object next() {
-                return ((NodeId) super.next()).getUUID();
+                NodeId nodeId = (NodeId) super.next();
+                removedNodeIds.add(nodeId);
+                return nodeId.getUUID();
             }
         }, new AbstractIteratorDecorator(add) {
             public Object next() {
@@ -363,6 +406,8 @@
                 if (state == null) {
                     return null;
                 }
+                addedNodeIds.add(state.getNodeId());
+                removedNodeIds.remove(state.getNodeId());
                 Document doc = null;
                 try {
                     doc = createDocument(state, getNamespaceMappings());
@@ -370,9 +415,38 @@
                     log.error("Exception while creating document for node: "
                             + state.getNodeId() + ": " + e.toString());
                 }
+                retrieveAggregateRoot(state, aggregateRoots);
                 return doc;
             }
         });
+
+        // remove any aggregateRoot nodes that are new
+        // and therefore already up-to-date
+        aggregateRoots.keySet().removeAll(addedNodeIds);
+
+        // based on removed NodeIds get affected aggregate root nodes
+        retrieveAggregateRoot(removedNodeIds, aggregateRoots);
+
+        // update aggregates if there are any affected
+        if (aggregateRoots.size() > 0) {
+            index.update(new AbstractIteratorDecorator(
+                    aggregateRoots.keySet().iterator()) {
+                public Object next() {
+                    return ((NodeId) super.next()).getUUID();
+                }
+            }, new AbstractIteratorDecorator(aggregateRoots.values().iterator()) {
+                public Object next() {
+                    NodeState state = (NodeState) super.next();
+                    try {
+                        return createDocument(state, getNamespaceMappings());
+                    } catch (RepositoryException e) {
+                        log.error("Exception while creating document for node: "
+                                + state.getNodeId() + ": " + e.toString());
+                    }
+                    return null;
+                }
+            });
+        }
     }
 
     /**
@@ -488,7 +562,7 @@
      * @return the namespace mappings for the internal representation.
      */
     public NamespaceMappings getNamespaceMappings() {
-        return index.getNamespaceMappings();
+        return nsMappings;
     }
 
     /**
@@ -563,7 +637,10 @@
         NodeIndexer indexer = new NodeIndexer(node,
                 getContext().getItemStateManager(), nsMappings, extractor);
         indexer.setSupportHighlighting(supportHighlighting);
-        return indexer.createDoc();
+        indexer.setIndexingConfiguration(indexingConfig);
+        Document doc = indexer.createDoc();
+        mergeAggregatedNodeIndexes(node, doc);
+        return doc;
     }
 
     /**
@@ -590,6 +667,193 @@
         return txtExtr;
     }
 
+    /**
+     * @return the fulltext indexing configuration or <code>null</code> if there
+     *         is no configuration.
+     */
+    protected IndexingConfiguration createIndexingConfiguration() {
+        Element docElement = getIndexingConfigurationDOM();
+        if (docElement == null) {
+            return null;
+        }
+        try {
+            IndexingConfiguration idxCfg = (IndexingConfiguration)
+                    indexingConfigurationClass.newInstance();
+            idxCfg.init(docElement, getContext());
+            return idxCfg;
+        } catch (Exception e) {
+            log.warn("Exception initializing indexing configuration from: " +
+                    indexingConfigPath, e);
+        }
+        log.warn(indexingConfigPath + " ignored.");
+        return null;
+    }
+
+    /**
+     * 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);
+        if (!config.exists()) {
+            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());
+            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);
+        }
+        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>.
+     */
+    protected void mergeAggregatedNodeIndexes(NodeState state, Document doc) {
+        if (indexingConfig != null) {
+            AggregateRule aggregateRules[] = indexingConfig.getAggregateRules();
+            if (aggregateRules == null) {
+                return;
+            }
+            try {
+                for (int i = 0; i < aggregateRules.length; i++) {
+                    NodeState[] aggregates = aggregateRules[i].getAggregatedNodeStates(state);
+                    if (aggregates == null) {
+                        continue;
+                    }
+                    for (int j = 0; j < aggregates.length; j++) {
+                        Document aDoc = createDocument(aggregates[j],
+                                getNamespaceMappings());
+                        // transfer fields to doc if there are any
+                        Field[] fulltextFields = aDoc.getFields(FieldNames.FULLTEXT);
+                        if (fulltextFields != null) {
+                            for (int k = 0; k < fulltextFields.length; k++) {
+                                doc.add(fulltextFields[k]);
+                            }
+                            doc.add(new Field(FieldNames.AGGREGATED_NODE_UUID,
+                                    aggregates[j].getNodeId().getUUID().toString(),
+                                    Field.Store.NO,
+                                    Field.Index.NO_NORMS));
+                        }
+                    }
+                    // only use first aggregate definition that matches
+                    break;
+                }
+            } catch (Exception e) {
+                // do not fail if aggregate cannot be created
+                log.warn("Exception while building indexing aggregate for " +
+                        "node with UUID: " + state.getNodeId().getUUID(), e);
+            }
+        }
+    }
+
+    /**
+     * 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. Key=NodeId,
+     *              value=NodeState.
+     */
+    protected void retrieveAggregateRoot(NodeState state, Map map) {
+        if (indexingConfig != null) {
+            AggregateRule aggregateRules[] = indexingConfig.getAggregateRules();
+            if (aggregateRules == null) {
+                return;
+            }
+            try {
+                for (int i = 0; i < aggregateRules.length; i++) {
+                    NodeState root = aggregateRules[i].getAggregateRoot(state);
+                    if (root != null) {
+                        map.put(root.getNodeId(), root);
+                        break;
+                    }
+                }
+            } catch (Exception e) {
+                log.warn("Unable to get aggregate root for " +
+                        state.getNodeId().getUUID(), e);
+            }
+        }
+    }
+
+    /**
+     * Retrieves the root of the indexing aggregate for <code>removedNodeIds</code>
+     * and puts it into <code>map</code>.
+     *
+     * @param removedNodeIds the ids of removed nodes.
+     * @param map            aggregate roots are collected in this map.
+     *                       Key=NodeId, value=NodeState.
+     */
+    protected void retrieveAggregateRoot(Set removedNodeIds, Map map) {
+        if (indexingConfig != null) {
+            AggregateRule aggregateRules[] = indexingConfig.getAggregateRules();
+            if (aggregateRules == null) {
+                return;
+            }
+            int found = 0;
+            long time = System.currentTimeMillis();
+            try {
+                IndexReader reader = index.getIndexReader();
+                try {
+                    Term aggregateUUIDs = new Term(
+                            FieldNames.AGGREGATED_NODE_UUID, "");
+                    TermDocs tDocs = reader.termDocs();
+                    try {
+                        ItemStateManager ism = getContext().getItemStateManager();
+                        for (Iterator it = removedNodeIds.iterator(); it.hasNext(); ) {
+                            NodeId id = (NodeId) it.next();
+                            aggregateUUIDs = aggregateUUIDs.createTerm(
+                                    id.getUUID().toString());
+                            tDocs.seek(aggregateUUIDs);
+                            while (tDocs.next()) {
+                                Document doc = reader.document(tDocs.doc());
+                                String uuid = doc.get(FieldNames.UUID);
+                                NodeId nId = new NodeId(UUID.fromString(uuid));
+                                map.put(nId, ism.getItemState(nId));
+                                found++;
+                            }
+                        }
+                    } finally {
+                        tDocs.close();
+                    }
+                } finally {
+                    reader.close();
+                }
+            } catch (Exception e) {
+                log.warn("Exception while retrieving aggregate roots", e);
+            }
+            time = System.currentTimeMillis() - time;
+            log.debug("Retrieved {} aggregate roots in {} ms.",
+                    new Integer(found), new Long(time));
+        }
+    }
+
     //----------------------------< internal >----------------------------------
 
     /**
@@ -1027,6 +1291,52 @@
      */
     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();
     }
 
     //----------------------------< internal >----------------------------------