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 >----------------------------------