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());
         }