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/10/24 14:13:53 UTC
svn commit: r587866 - in /jackrabbit/trunk/jackrabbit-core/src/main:
java/org/apache/jackrabbit/core/query/
java/org/apache/jackrabbit/core/query/lucene/
java/org/apache/jackrabbit/core/query/sql/
java/org/apache/jackrabbit/core/query/xpath/ javacc/sql/
Author: mreutegg
Date: Wed Oct 24 05:13:51 2007
New Revision: 587866
URL: http://svn.apache.org/viewvc?rev=587866&view=rev
Log:
JCR-1184: Introduce spellchecker functionality based on content in the workspace
Added:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java (with props)
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java (with props)
Modified:
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryConstants.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PreparedQueryImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SearchIndex.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
jackrabbit/trunk/jackrabbit-core/src/main/javacc/sql/JCRSQL.jjt
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryConstants.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryConstants.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryConstants.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryConstants.java Wed Oct 24 05:13:51 2007
@@ -151,4 +151,11 @@
* SQL: SIMILAR(path_string)
*/
int OPERATION_SIMILAR = OPERATION_NOT_NULL + 1;
+
+ /**
+ * spellcheck operation:
+ * XPath: rep:spellcheck(string_literal)
+ * SQL: SPELLCHECK(string_literal)
+ */
+ int OPERATION_SPELLCHECK = OPERATION_SIMILAR + 1;
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/QueryTreeDump.java Wed Oct 24 05:13:51 2007
@@ -236,6 +236,8 @@
buffer.append("IS NULL");
} else if (node.getOperation() == QueryConstants.OPERATION_SIMILAR) {
buffer.append("similarity");
+ } else if (node.getOperation() == QueryConstants.OPERATION_SPELLCHECK) {
+ buffer.append("spellcheck");
} else {
buffer.append("!!UNKNOWN OPERATION!!");
}
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java?rev=587866&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java Wed Oct 24 05:13:51 2007
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+/**
+ * <code>TraversingQueryNodeVisitor</code> implements a base class for a
+ * traversing query node visitor.
+ */
+public class TraversingQueryNodeVisitor extends DefaultQueryNodeVisitor {
+
+ public Object visit(OrQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+
+ public Object visit(AndQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+
+ public Object visit(QueryRootNode node, Object data) {
+ PathQueryNode pathNode = node.getLocationNode();
+ if (pathNode != null) {
+ pathNode.accept(this, data);
+ }
+ OrderQueryNode orderNode = node.getOrderNode();
+ if (orderNode != null) {
+ orderNode.accept(this, data);
+ }
+ return data;
+ }
+
+ public Object visit(NotQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+
+ public Object visit(PathQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+
+ public Object visit(LocationStepQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+
+ public Object visit(DerefQueryNode node, Object data) {
+ return node.acceptOperands(this, data);
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/TraversingQueryNodeVisitor.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/LuceneQueryBuilder.java Wed Oct 24 05:13:51 2007
@@ -657,7 +657,8 @@
}
if (node.getRelativePath() == null &&
- node.getOperation() != QueryConstants.OPERATION_SIMILAR) {
+ node.getOperation() != QueryConstants.OPERATION_SIMILAR &&
+ node.getOperation() != QueryConstants.OPERATION_SPELLCHECK) {
exceptions.add(new InvalidQueryException("@* not supported in predicate"));
return data;
}
@@ -885,6 +886,9 @@
query = new SimilarityQuery(uuid, analyzer);
break;
case QueryConstants.OPERATION_NOT_NULL:
+ query = createMatchAllQuery(field);
+ break;
+ case QueryConstants.OPERATION_SPELLCHECK:
query = createMatchAllQuery(field);
break;
default:
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PreparedQueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PreparedQueryImpl.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PreparedQueryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/PreparedQueryImpl.java Wed Oct 24 05:13:51 2007
@@ -121,7 +121,8 @@
}
return new QueryResultImpl(index, itemMgr,
session.getNamespaceResolver(), session.getAccessManager(),
- this, query, selectProps, orderProps, orderSpecs,
+ // TODO: spell suggestion missing
+ this, query, null, selectProps, orderProps, orderSpecs,
getRespectDocumentOrder(), offset, limit);
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryImpl.java Wed Oct 24 05:13:51 2007
@@ -136,7 +136,8 @@
return new QueryResultImpl(index, itemMgr,
session.getNamespaceResolver(), session.getAccessManager(),
- this, query, getSelectProperties(), orderProperties, ascSpecs,
+ this, query, new SpellSuggestion(index.getSpellChecker(), root),
+ getSelectProperties(), orderProperties, ascSpecs,
getRespectDocumentOrder(), offset, limit);
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/QueryResultImpl.java Wed Oct 24 05:13:51 2007
@@ -80,6 +80,11 @@
protected final Query query;
/**
+ * The spell suggestion or <code>null</code> if not available.
+ */
+ protected final SpellSuggestion spellSuggestion;
+
+ /**
* The select properties
*/
protected final QName[] selectProps;
@@ -137,22 +142,26 @@
/**
* Creates a new query result.
*
- * @param index the search index where the query is executed.
- * @param itemMgr the item manager of the session executing the
- * query.
- * @param resolver the namespace resolver of the session executing the
- * query.
- * @param accessMgr the access manager of the session executiong the
- * query.
- * @param queryImpl the query instance which created this query result.
- * @param query the lucene query to execute on the index.
- * @param selectProps the select properties of the query.
- * @param orderProps the names of the order properties.
- * @param orderSpecs the order specs, one for each order property name.
- * @param documentOrder if <code>true</code> the result is returned in
- * document order.
- * @param limit the maximum result size
- * @param offset the offset in the total result set
+ * @param index the search index where the query is executed.
+ * @param itemMgr the item manager of the session executing the
+ * query.
+ * @param resolver the namespace resolver of the session executing
+ * the query.
+ * @param accessMgr the access manager of the session executiong the
+ * query.
+ * @param queryImpl the query instance which created this query
+ * result.
+ * @param query the lucene query to execute on the index.
+ * @param spellSuggestion the spell suggestion or <code>null</code> if none
+ * is available.
+ * @param selectProps the select properties of the query.
+ * @param orderProps the names of the order properties.
+ * @param orderSpecs the order specs, one for each order property
+ * name.
+ * @param documentOrder if <code>true</code> the result is returned in
+ * document order.
+ * @param limit the maximum result size
+ * @param offset the offset in the total result set
*/
public QueryResultImpl(SearchIndex index,
ItemManager itemMgr,
@@ -160,6 +169,7 @@
AccessManager accessMgr,
AbstractQueryImpl queryImpl,
Query query,
+ SpellSuggestion spellSuggestion,
QName[] selectProps,
QName[] orderProps,
boolean[] orderSpecs,
@@ -172,6 +182,7 @@
this.accessMgr = accessMgr;
this.queryImpl = queryImpl;
this.query = query;
+ this.spellSuggestion = spellSuggestion;
this.selectProps = selectProps;
this.orderProps = orderProps;
this.orderSpecs = orderSpecs;
@@ -218,8 +229,8 @@
throw new RepositoryException(e);
}
}
- return new RowIteratorImpl(getNodeIterator(),
- selectProps, resolver, excerptProvider);
+ return new RowIteratorImpl(getNodeIterator(), selectProps,
+ resolver, excerptProvider, spellSuggestion);
}
/**
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/RowIteratorImpl.java Wed Oct 24 05:13:51 2007
@@ -24,9 +24,7 @@
import org.apache.jackrabbit.name.QName;
import org.apache.jackrabbit.name.NameFormat;
import org.apache.jackrabbit.name.NoPrefixDeclaredException;
-import org.apache.jackrabbit.value.LongValue;
-import org.apache.jackrabbit.value.PathValue;
-import org.apache.jackrabbit.value.StringValue;
+import org.apache.jackrabbit.value.ValueFactoryImpl;
import org.apache.jackrabbit.util.ISO9075;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;
@@ -37,6 +35,7 @@
import javax.jcr.RepositoryException;
import javax.jcr.Value;
import javax.jcr.PathNotFoundException;
+import javax.jcr.ValueFactory;
import javax.jcr.query.Row;
import javax.jcr.query.RowIterator;
import java.util.Arrays;
@@ -57,11 +56,22 @@
private static final Logger log = LoggerFactory.getLogger(RowIteratorImpl.class);
/**
+ * The value factory.
+ */
+ private static final ValueFactory VALUE_FACTORY = ValueFactoryImpl.getInstance();
+
+ /**
* The name of the excerpt function without prefix but with left parenthesis.
*/
private static final String EXCERPT_FUNC_LPAR = "excerpt(";
/**
+ * The name of the spell check function without prefix but with left
+ * parenthesis.
+ */
+ private static final String SPELLCHECK_FUNC_LPAR = "spellcheck(";
+
+ /**
* The start QName for the rep:excerpt function: rep:excerpt(
*/
private static final QName REP_EXCERPT_LPAR = new QName(
@@ -83,11 +93,16 @@
private final NamespaceResolver resolver;
/**
- * The excerpt provider.
+ * The excerpt provider or <code>null</code> if none is available.
*/
private final ExcerptProvider excerptProvider;
/**
+ * The spell suggestion or <code>null</code> if none is available.
+ */
+ private final SpellSuggestion spellSuggestion;
+
+ /**
* Creates a new <code>RowIteratorImpl</code> that iterates over the result
* nodes.
*
@@ -98,29 +113,33 @@
* <code>Session</code>.
*/
RowIteratorImpl(ScoreNodeIterator nodes, QName[] properties, NamespaceResolver resolver) {
- this(nodes, properties, resolver, null);
+ this(nodes, properties, resolver, null, null);
}
/**
* Creates a new <code>RowIteratorImpl</code> that iterates over the result
* nodes.
*
- * @param nodes a <code>ScoreNodeIterator</code> that contains the
- * nodes of the query result.
- * @param properties <code>QName</code> of the select properties.
- * @param resolver <code>NamespaceResolver</code> of the user
- * <code>Session</code>.
- * @param exProvider the excerpt provider associated with the query result
- * that created this row iterator.
+ * @param nodes a <code>ScoreNodeIterator</code> that contains the
+ * nodes of the query result.
+ * @param properties <code>QName</code> of the select properties.
+ * @param resolver <code>NamespaceResolver</code> of the user
+ * <code>Session</code>.
+ * @param exProvider the excerpt provider associated with the query
+ * result that created this row iterator.
+ * @param spellSuggestion the spell suggestion associated with the query
+ * result or <code>null</code> if none is available.
*/
RowIteratorImpl(ScoreNodeIterator nodes,
QName[] properties,
NamespaceResolver resolver,
- ExcerptProvider exProvider) {
+ ExcerptProvider exProvider,
+ SpellSuggestion spellSuggestion) {
this.nodes = nodes;
this.properties = properties;
this.resolver = resolver;
this.excerptProvider = exProvider;
+ this.spellSuggestion = spellSuggestion;
}
/**
@@ -252,7 +271,7 @@
PropertyImpl prop = node.getProperty(properties[i]);
if (!prop.getDefinition().isMultiple()) {
if (prop.getDefinition().getRequiredType() == PropertyType.UNDEFINED) {
- tmp[i] = new StringValue(prop.getString());
+ tmp[i] = VALUE_FACTORY.createValue(prop.getString());
} else {
tmp[i] = prop.getValue();
}
@@ -261,13 +280,16 @@
tmp[i] = null;
}
} else {
- // property not set or jcr:path / jcr:score / jcr:highlight
+ // property not set or one of the following:
+ // jcr:path / jcr:score / rep:excerpt / rep:spellcheck
if (QName.JCR_PATH.equals(properties[i])) {
- tmp[i] = PathValue.valueOf(node.getPath());
+ tmp[i] = VALUE_FACTORY.createValue(node.getPath(), PropertyType.PATH);
} else if (QName.JCR_SCORE.equals(properties[i])) {
- tmp[i] = new LongValue(Math.round(score * 1000f));
+ tmp[i] = VALUE_FACTORY.createValue(Math.round(score * 1000f));
} else if (isExcerptFunction(properties[i])) {
tmp[i] = getExcerpt();
+ } else if (isSpellCheckFunction(properties[i])) {
+ tmp[i] = getSpellCheckedStatement();
} else {
tmp[i] = null;
}
@@ -313,18 +335,21 @@
if (node.hasProperty(prop)) {
Property p = node.getProperty(prop);
if (p.getDefinition().getRequiredType() == PropertyType.UNDEFINED) {
- return new StringValue(p.getString());
+ return VALUE_FACTORY.createValue(p.getString());
} else {
return p.getValue();
}
} else {
- // either jcr:score, jcr:path or not set
+ // either jcr:score, jcr:path, rep:excerpt,
+ // rep:spellcheck or not set
if (QName.JCR_PATH.equals(prop)) {
- return PathValue.valueOf(node.getPath());
+ return VALUE_FACTORY.createValue(node.getPath(), PropertyType.PATH);
} else if (QName.JCR_SCORE.equals(prop)) {
- return new LongValue(Math.round(score * 1000f));
+ return VALUE_FACTORY.createValue(Math.round(score * 1000f));
} else if (isExcerptFunction(prop)) {
return getExcerpt();
+ } else if (isSpellCheckFunction(prop)) {
+ return getSpellCheckedStatement();
} else {
return null;
}
@@ -425,7 +450,7 @@
time = System.currentTimeMillis() - time;
log.debug("Created excerpt in {} ms.", new Long(time));
if (excerpt != null) {
- return new StringValue(excerpt);
+ return VALUE_FACTORY.createValue(excerpt);
} else {
return null;
}
@@ -450,8 +475,42 @@
text = hep.highlight(text);
time = System.currentTimeMillis() - time;
log.debug("Highlighted text in {} ms.", new Long(time));
- return new StringValue(text);
+ return VALUE_FACTORY.createValue(text);
} catch (IOException e) {
+ return null;
+ }
+ }
+
+ /**
+ * @param name a QName.
+ * @return <code>true</code> if <code>name</code> is the rep:spellcheck
+ * function, <code>false</code> otherwise.
+ */
+ private boolean isSpellCheckFunction(QName name) {
+ return name.getNamespaceURI().equals(QName.NS_REP_URI) &&
+ name.getLocalName().startsWith(SPELLCHECK_FUNC_LPAR);
+ }
+
+ /**
+ * Returns the spell checked string of the first relation query node
+ * with a spellcheck operation.
+ *
+ * @return a StringValue or <code>null</code> if the spell checker
+ * thinks the words are spelled correctly. This method also
+ * returns <code>null</code> if no spell checker is configured.
+ */
+ private Value getSpellCheckedStatement() {
+ String v = null;
+ if (spellSuggestion != null) {
+ try {
+ v = spellSuggestion.getSuggestion();
+ } catch (IOException e) {
+ log.warn("Spell checking failed", e);
+ }
+ }
+ if (v != null) {
+ return VALUE_FACTORY.createValue(v);
+ } else {
return null;
}
}
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?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- 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 Wed Oct 24 05:13:51 2007
@@ -330,6 +330,17 @@
private IndexFormatVersion indexFormatVersion;
/**
+ * The class that implements {@link SpellChecker}.
+ */
+ private Class spellCheckerClass;
+
+ /**
+ * The spell checker for this query handler or <code>null</code> if none is
+ * configured.
+ */
+ private SpellChecker spellChecker;
+
+ /**
* Indicates if this <code>SearchIndex</code> is closed and cannot be used
* anymore.
*/
@@ -414,6 +425,10 @@
log.warn("Failed to run consistency check on index: " + e);
}
}
+
+ // initialize spell checker
+ spellChecker = createSpellChecker();
+
log.info("Index initialized: {} Version: {}",
new Object[]{path, index.getIndexFormatVersion()});
}
@@ -580,6 +595,9 @@
if (extractor instanceof PooledTextExtractor) {
((PooledTextExtractor) extractor).shutdown();
}
+ if (spellChecker != null) {
+ spellChecker.close();
+ }
index.close();
getContext().destroy();
closed = true;
@@ -688,6 +706,14 @@
}
/**
+ * @return the spell checker of this search index. If none is configured
+ * this method returns <code>null</code>.
+ */
+ public SpellChecker getSpellChecker() {
+ return spellChecker;
+ }
+
+ /**
* Returns an index reader for this search index. The caller of this method
* is responsible for closing the index reader when he is finished using
* it.
@@ -874,6 +900,26 @@
}
/**
+ * Creates a spell checker for this query handler.
+ *
+ * @return the spell checker or <code>null</code> if none is configured or
+ * an error occurs.
+ */
+ protected SpellChecker createSpellChecker() {
+ SpellChecker spCheck = null;
+ if (spellCheckerClass != null) {
+ try {
+ spCheck = (SpellChecker) spellCheckerClass.newInstance();
+ spCheck.init(this);
+ } catch (Exception e) {
+ log.warn("Exception initializing spell checker: " +
+ spellCheckerClass, e);
+ }
+ }
+ return spCheck;
+ }
+
+ /**
* Returns the document element of the indexing configuration or
* <code>null</code> if there is no indexing configuration.
*
@@ -1554,6 +1600,37 @@
public String getSynonymProviderClass() {
return synonymProviderClass != null ?
synonymProviderClass.getName() : null;
+ }
+
+ /**
+ * Sets the name of the class that implements {@link SpellChecker}. The
+ * default value is <code>null</code> (none set).
+ *
+ * @param className name of the class that implements {@link SpellChecker}.
+ */
+ public void setSpellCheckerClass(String className) {
+ try {
+ Class clazz = Class.forName(className);
+ if (SpellChecker.class.isAssignableFrom(clazz)) {
+ spellCheckerClass = clazz;
+ } else {
+ log.warn("Invalid value for spellCheckerClass, {} " +
+ "does not implement SpellChecker interface.",
+ className);
+ }
+ } catch (ClassNotFoundException e) {
+ log.warn("Invalid value for spellCheckerClass, class {} " +
+ "not found.", className);
+ }
+ }
+
+ /**
+ * @return the class name of the spell checker implementation or
+ * <code>null</code> if none is set.
+ */
+ public String getSpellCheckerClass() {
+ return spellCheckerClass != null ?
+ spellCheckerClass.getName() : null;
}
/**
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java?rev=587866&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java Wed Oct 24 05:13:51 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.query.QueryHandler;
+import org.apache.jackrabbit.core.query.QueryRootNode;
+
+import java.io.IOException;
+
+/**
+ * <code>SpellChecker</code> defines an interface to run a spellchecker over
+ * a fulltext query statement.
+ */
+public interface SpellChecker {
+
+ /**
+ * Initializes this spell checker with an abstract query tree.
+ *
+ * @param handler the query handler that created this spell checker.
+ * @throws IOException if an error occurs while initializing the spell
+ * checker.
+ */
+ public void init(QueryHandler handler) throws IOException;
+
+ /**
+ * Runs the spell checker over the first spellcheck relation query node in
+ * the abstract query tree and returns a suggestion in case this
+ * spellchecker thinks the words are misspelled. If the spellchecker
+ * determines that the words are spelled correctly <code>null</code> is
+ * returned.
+ *
+ * @param aqt the abstract query tree, which may contain a relation query
+ * node with a spellcheck operation.
+ * @return a suggestion or <code>null</code> if this spell checker
+ * determines that the fulltext query statement is spelled
+ * correctly.
+ */
+ public String check(QueryRootNode aqt) throws IOException;
+
+ /**
+ * Closes this spell checker and allows it to free resources.
+ */
+ public void close();
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellChecker.java
------------------------------------------------------------------------------
svn:eol-style = native
Added: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java?rev=587866&view=auto
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java (added)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java Wed Oct 24 05:13:51 2007
@@ -0,0 +1,65 @@
+/*
+ * 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.query.QueryRootNode;
+
+import java.io.IOException;
+
+/**
+ * <code>SpellSuggestion</code> implements a spell suggestion, which uses the
+ * spell checker.
+ */
+class SpellSuggestion {
+
+ /**
+ * The spell checker.
+ */
+ private final SpellChecker spellChecker;
+
+ /**
+ * The abstract query tree.
+ */
+ private final QueryRootNode root;
+
+ /**
+ * Creates a new spell suggestion.
+ *
+ * @param spellChecker the spell checker or <code>null</code> if none is
+ * available.
+ * @param root the abstract query tree.
+ */
+ SpellSuggestion(SpellChecker spellChecker, QueryRootNode root) {
+ this.spellChecker = spellChecker;
+ this.root = root;
+ }
+
+ /**
+ * @return a suggestion for the spellcheck query node in the abstract query
+ * tree passed in the constructor of this <code>SpellSuggestion</code>.
+ * This method returns <code>null</code> if the spell checker thinks
+ * the spelling is correct or no spell checker was provided.
+ * @throws IOException if an error occurs while checking the spelling.
+ */
+ public String getSuggestion() throws IOException {
+ if (spellChecker != null) {
+ return spellChecker.check(root);
+ } else {
+ return null;
+ }
+ }
+}
Propchange: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/lucene/SpellSuggestion.java
------------------------------------------------------------------------------
svn:eol-style = native
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/JCRSQLQueryBuilder.java Wed Oct 24 05:13:51 2007
@@ -428,7 +428,16 @@
predicateNode = createRelationQueryNode(parent,
identifier, type, star);
} else if (type == QueryConstants.OPERATION_SIMILAR) {
- predicateNode = createRelationQueryNode(parent, null, type,
+ ASTLiteral literal;
+ if (node.children.length == 1) {
+ literal = (ASTLiteral) node.children[0];
+ } else {
+ literal = (ASTLiteral) node.children[1];
+ }
+ predicateNode = createRelationQueryNode(parent, identifier, type, literal);
+ } else if (type == QueryConstants.OPERATION_SPELLCHECK) {
+ predicateNode = createRelationQueryNode(parent,
+ QName.JCR_PRIMARYTYPE, type,
(ASTLiteral) node.children[0]);
} else {
throw new IllegalArgumentException("Unknown operation type: " + type);
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/sql/QueryFormat.java Wed Oct 24 05:13:51 2007
@@ -396,44 +396,65 @@
}
public Object visit(RelationQueryNode node, Object data) {
- Path relPath = node.getRelativePath();
- if (relPath.getLength() > 1) {
- exceptions.add(new InvalidQueryException("Child axis not supported in SQL"));
- return data;
- }
StringBuffer sb = (StringBuffer) data;
try {
StringBuffer propName = new StringBuffer();
- appendName(relPath.getNameElement().getName(), resolver, propName);
+ Path relPath = node.getRelativePath();
+ if (relPath == null) {
+ propName.append(".");
+ } else if (relPath.getLength() > 1) {
+ exceptions.add(new InvalidQueryException("Child axis not supported in SQL"));
+ return data;
+ } else {
+ appendName(relPath.getNameElement().getName(), resolver, propName);
+ }
// surround name with property function
node.acceptOperands(this, propName);
- sb.append(propName);
if (node.getOperation() == OPERATION_EQ_VALUE || node.getOperation() == OPERATION_EQ_GENERAL) {
+ sb.append(propName);
sb.append(" = ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_GE_VALUE || node.getOperation() == OPERATION_GE_GENERAL) {
+ sb.append(propName);
sb.append(" >= ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_GT_VALUE || node.getOperation() == OPERATION_GT_GENERAL) {
+ sb.append(propName);
sb.append(" > ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LE_VALUE || node.getOperation() == OPERATION_LE_GENERAL) {
+ sb.append(propName);
sb.append(" <= ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LIKE) {
+ sb.append(propName);
sb.append(" LIKE ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_LT_VALUE || node.getOperation() == OPERATION_LT_GENERAL) {
+ sb.append(propName);
sb.append(" < ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_NE_VALUE || node.getOperation() == OPERATION_NE_GENERAL) {
+ sb.append(propName);
sb.append(" <> ");
appendValue(node, sb);
} else if (node.getOperation() == OPERATION_NULL) {
+ sb.append(propName);
sb.append(" IS NULL");
} else if (node.getOperation() == OPERATION_NOT_NULL) {
+ sb.append(propName);
sb.append(" IS NOT NULL");
+ } else if (node.getOperation() == OPERATION_SIMILAR) {
+ sb.append("SIMILAR(");
+ sb.append(propName);
+ sb.append(", ");
+ appendValue(node, sb);
+ sb.append(")");
+ } else if (node.getOperation() == OPERATION_SPELLCHECK) {
+ sb.append("SPELLCHECK(");
+ appendValue(node, sb);
+ sb.append(")");
} else {
exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/QueryFormat.java Wed Oct 24 05:13:51 2007
@@ -337,7 +337,9 @@
StringBuffer propPath = new StringBuffer();
// only encode if not position function
Path relPath = node.getRelativePath();
- if (relPath.getNameElement().getName().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
+ if (relPath == null) {
+ propPath.append(".");
+ } else if (relPath.getNameElement().getName().equals(XPathQueryBuilder.FN_POSITION_FULL)) {
NameFormat.format(XPathQueryBuilder.FN_POSITION_FULL, resolver, propPath);
} else {
Path.PathElement[] elements = relPath.getElements();
@@ -345,7 +347,7 @@
for (int i = 0; i < elements.length; i++) {
propPath.append(slash);
slash = "/";
- if (i == elements.length - 1) {
+ if (i == elements.length - 1 && node.getOperation() != OPERATION_SIMILAR) {
propPath.append("@");
}
if (elements[i].getName().equals(RelationQueryNode.STAR_NAME_TEST)) {
@@ -408,6 +410,15 @@
sb.append("(").append(propPath).append(")");
} else if (node.getOperation() == OPERATION_NOT_NULL) {
sb.append(propPath);
+ } else if (node.getOperation() == OPERATION_SIMILAR) {
+ NameFormat.format(XPathQueryBuilder.REP_SIMILAR, resolver, sb);
+ sb.append("(").append(propPath).append(", ");
+ appendValue(node, sb);
+ } else if (node.getOperation() == OPERATION_SPELLCHECK) {
+ NameFormat.format(XPathQueryBuilder.REP_SPELLCHECK, resolver, sb);
+ sb.append("(");
+ appendValue(node, sb);
+ sb.append(")");
} else {
exceptions.add(new InvalidQueryException("Invalid operation: " + node.getOperation()));
}
Modified: jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/java/org/apache/jackrabbit/core/query/xpath/XPathQueryBuilder.java Wed Oct 24 05:13:51 2007
@@ -155,6 +155,11 @@
static final QName REP_SIMILAR = new QName(QName.NS_REP_URI, "similar");
/**
+ * QName for rep:spellcheck
+ */
+ static final QName REP_SPELLCHECK = new QName(QName.NS_REP_URI, "spellcheck");
+
+ /**
* String constant for operator 'eq'
*/
private static final String OP_EQ = "eq";
@@ -1007,6 +1012,33 @@
} else {
exceptions.add(new InvalidQueryException(
"Wrong number of arguments for rep:similar()"));
+ }
+ } else if (NameFormat.format(REP_SPELLCHECK, resolver).equals(fName)
+ && queryNode.getType() != QueryNode.TYPE_PATH) {
+ if (node.jjtGetNumChildren() == 2) {
+ if (queryNode instanceof NAryQueryNode) {
+ NAryQueryNode parent = (NAryQueryNode) queryNode;
+ RelationQueryNode rel = factory.createRelationQueryNode(
+ parent, RelationQueryNode.OPERATION_SPELLCHECK);
+ parent.addOperand(rel);
+
+ // get string to check
+ node.jjtGetChild(1).jjtAccept(this, rel);
+ // check if string is set
+ if (rel.getStringValue() == null) {
+ exceptions.add(new InvalidQueryException(
+ "Argument for rep:spellcheck() must be of type string"));
+ }
+
+ // set a dummy property name
+ rel.addPathElement(Path.PathElement.create(QName.JCR_PRIMARYTYPE));
+ } else {
+ exceptions.add(new InvalidQueryException(
+ "Unsupported location for rep:spellcheck()"));
+ }
+ } else {
+ exceptions.add(new InvalidQueryException(
+ "Wrong number of arguments for rep:spellcheck()"));
}
} else if (queryNode.getType() == QueryNode.TYPE_RELATION) {
// use function name as name of a pseudo property in a relation
Modified: jackrabbit/trunk/jackrabbit-core/src/main/javacc/sql/JCRSQL.jjt
URL: http://svn.apache.org/viewvc/jackrabbit/trunk/jackrabbit-core/src/main/javacc/sql/JCRSQL.jjt?rev=587866&r1=587865&r2=587866&view=diff
==============================================================================
--- jackrabbit/trunk/jackrabbit-core/src/main/javacc/sql/JCRSQL.jjt (original)
+++ jackrabbit/trunk/jackrabbit-core/src/main/javacc/sql/JCRSQL.jjt Wed Oct 24 05:13:51 2007
@@ -109,6 +109,7 @@
| < EXCERPT: "EXCERPT" >
| < SIMILAR: "SIMILAR" >
| < CONTAINS: "CONTAINS" >
+| < SPELLCHECK: "SPELLCHECK" >
}
@@ -274,7 +275,13 @@
{}
{
(<ASTERISK>)
- | ((ExcerptFunction() | Identifier() (<PERIOD> Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); } )?) (<COMMA> (ExcerptFunction() | Identifier() (<PERIOD> Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); } )?) )*)
+ | ( SelectItem() ( <COMMA> SelectItem() )* )
+}
+
+void SelectItem() #void :
+{}
+{
+ (ExcerptFunction() | Identifier() (<PERIOD> Identifier() { Node n = jjtree.popNode(); jjtree.popNode(); jjtree.pushNode(n); } )?)
}
void TableExpression() #void :
@@ -373,6 +380,17 @@
jjtree.pushNode(s);
} ")"
)
+ |
+ (
+ <SPELLCHECK> "(" { jjtThis.setOperationType(QueryConstants.OPERATION_SPELLCHECK); }
+ value = CharStringLiteral()
+ {
+ ASTLiteral stmt = new ASTLiteral(JJTLITERAL);
+ stmt.setType(QueryConstants.TYPE_STRING);
+ stmt.setValue(value);
+ jjtree.pushNode(stmt);
+ } ")"
+ )
)
}
@@ -571,13 +589,18 @@
{
Token t = null;
QName name = null;
+ boolean pseudoProperty = false;
}
{
(
- t = <REGULAR_IDENTIFIER>
+ t = <REGULAR_IDENTIFIER> ( <LEFT_PAREN> <RIGHT_PAREN> { pseudoProperty = true; } ) ?
{
try {
- jjtThis.setName(NameFormat.parse(t.image, resolver));
+ String jcrName = t.image;
+ if (pseudoProperty) {
+ jcrName += "()";
+ }
+ jjtThis.setName(NameFormat.parse(jcrName, resolver));
} catch (NameException e) {
throw new ParseException(e.getMessage());
}