You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2012/07/01 22:22:30 UTC

svn commit: r1356004 - in /jackrabbit/oak/trunk/oak-core/src: main/java/org/apache/jackrabbit/oak/plugins/lucene/ main/java/org/apache/jackrabbit/oak/query/index/ main/java/org/apache/jackrabbit/oak/spi/ test/java/org/apache/jackrabbit/oak/plugins/lucene/

Author: jukka
Date: Sun Jul  1 20:22:29 2012
New Revision: 1356004

URL: http://svn.apache.org/viewvc?rev=1356004&view=rev
Log:
OAK-154: Full text search index

Add a Lucene-based QueryIndexProvider

Added:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndex.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexProvider.java
Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/OakDirectory.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java
    jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneEditorTest.java

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndex.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndex.java?rev=1356004&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndex.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndex.java Sun Jul  1 20:22:29 2012
@@ -0,0 +1,188 @@
+/*
+ * 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.oak.plugins.lucene;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+import org.apache.jackrabbit.oak.spi.Cursor;
+import org.apache.jackrabbit.oak.spi.Filter;
+import org.apache.jackrabbit.oak.spi.Filter.PropertyRestriction;
+import org.apache.jackrabbit.oak.spi.QueryIndex;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause.Occur;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TermRangeQuery;
+import org.apache.lucene.search.TopDocs;
+import org.apache.lucene.store.Directory;
+
+public class LuceneIndex implements QueryIndex {
+
+    private final NodeStore store;
+
+    private final String[] path;
+
+    public LuceneIndex(NodeStore store, String... path) {
+        this.store = store;
+        this.path = path;
+    }
+
+    @Override
+    public String getIndexName() {
+        return "lucene";
+    }
+
+    @Override
+    public double getCost(Filter filter) {
+        return 1.0;
+    }
+
+    @Override
+    public String getPlan(Filter filter) {
+        return getQuery(filter).toString();
+    }
+
+    @Override
+    public Cursor query(Filter filter, String revisionId) {
+        try {
+            Directory directory =
+                    new OakDirectory(store, store.getRoot(), path);
+            try {
+                IndexReader reader = IndexReader.open(directory);
+                try {
+                    IndexSearcher searcher = new IndexSearcher(reader);
+                    try {
+                        Collection<String> paths = new ArrayList<String>();
+
+                        Query query = getQuery(filter);
+                        if (query != null) {
+                            TopDocs docs = searcher.search(query, Integer.MAX_VALUE);
+                            for (ScoreDoc doc : docs.scoreDocs) {
+                                String path = reader.document(doc.doc).get(":path");
+                                if ("".equals(path)) {
+                                    paths.add("/");
+                                } else if (path != null) {
+                                    paths.add(path);
+                                }
+                            }
+                        }
+
+                        return new PathCursor(paths);
+                    } finally {
+                        searcher.close();
+                    }
+                } finally {
+                    reader.close();
+                }
+            } finally {
+                directory.close();
+            }
+        } catch (IOException e) {
+            return new PathCursor(Collections.<String>emptySet());
+        }
+    }
+
+    private Query getQuery(Filter filter) {
+        List<Query> qs = new ArrayList<Query>();
+
+        String path = filter.getPath();
+        if (path.equals("/")) {
+            path = "";
+        }
+        switch (filter.getPathRestriction()) {
+        case ALL_CHILDREN:
+            qs.add(new PrefixQuery(new Term(":path", path + "/")));
+            break;
+        case DIRECT_CHILDREN:
+            qs.add(new PrefixQuery(new Term(":path", path + "/"))); // FIXME
+            break;
+        case EXACT:
+            qs.add(new TermQuery(new Term(":path", path)));
+            break;
+        case PARENT:
+            int slash = path.lastIndexOf('/');
+            if (slash != -1) {
+                String parent = path.substring(0, slash);
+                qs.add(new TermQuery(new Term(":path", parent)));
+            } else {
+                return null; // there's no parent of the root node
+            }
+            break;
+        }
+
+        for (PropertyRestriction pr : filter.getPropertyRestrictions()) {
+            String name = pr.propertyName;
+            String first = pr.first.getString();
+            String last = pr.last.getString();
+            if (first .equals(last) && pr.firstIncluding && pr.lastIncluding) {
+                qs.add(new TermQuery(new Term(name, first)));
+            } else {
+                qs.add(new TermRangeQuery(
+                        name, first, last, pr.firstIncluding, pr.lastIncluding));
+            }
+        }
+
+        if (qs.size() > 1) {
+            BooleanQuery bq = new BooleanQuery();
+            for (Query q : qs) {
+                bq.add(q, Occur.MUST);
+            }
+            return bq;
+        } else {
+            return qs.get(1);
+        }
+    }
+
+    private static class PathCursor implements Cursor {
+
+        private final Iterator<String> iterator;
+
+        private String path = null;
+
+        public PathCursor(Collection<String> paths) {
+            this.iterator = paths.iterator();
+        }
+
+        public boolean next() {
+            if (iterator.hasNext()) {
+                path = iterator.next();
+                return true;
+            } else {
+                path = null;
+                return false;
+            }
+        }
+
+        @Override
+        public String currentPath() {
+            return path;
+        }
+
+    };
+
+}

Added: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexProvider.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexProvider.java?rev=1356004&view=auto
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexProvider.java (added)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneIndexProvider.java Sun Jul  1 20:22:29 2012
@@ -0,0 +1,37 @@
+/*
+ * 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.oak.plugins.lucene;
+
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.jackrabbit.mk.api.MicroKernel;
+import org.apache.jackrabbit.oak.kernel.KernelNodeStore;
+import org.apache.jackrabbit.oak.spi.QueryIndex;
+import org.apache.jackrabbit.oak.spi.QueryIndexProvider;
+import org.apache.jackrabbit.oak.spi.commit.EmptyEditor;
+import org.apache.jackrabbit.oak.spi.state.NodeStore;
+
+public class LuceneIndexProvider implements QueryIndexProvider {
+
+    @Override
+    public List<QueryIndex> getQueryIndexes(MicroKernel mk) {
+        NodeStore store = new KernelNodeStore(mk, EmptyEditor.INSTANCE);
+        return Collections.<QueryIndex>singletonList(new LuceneIndex(store));
+    }
+
+}

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/OakDirectory.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/OakDirectory.java?rev=1356004&r1=1356003&r2=1356004&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/OakDirectory.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/plugins/lucene/OakDirectory.java Sun Jul  1 20:22:29 2012
@@ -60,7 +60,7 @@ class OakDirectory extends Directory {
         NodeState state = root;
         builders[0] = store.getBuilder(state);
         for (int i = 0; i < path.length; i++) {
-            NodeState child = root.getChildNode(path[i]);
+            NodeState child = state.getChildNode(path[i]);
             if (child == null) {
                 builders[i + 1] = store.getBuilder(null);
                 state = builders[i + 1].getNodeState();

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java?rev=1356004&r1=1356003&r2=1356004&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/query/index/FilterImpl.java Sun Jul  1 20:22:29 2012
@@ -24,6 +24,7 @@ import org.apache.jackrabbit.oak.query.a
 import org.apache.jackrabbit.oak.query.ast.SelectorImpl;
 import org.apache.jackrabbit.oak.spi.Filter;
 
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map.Entry;
 
@@ -133,6 +134,11 @@ public class FilterImpl implements Filte
         return selector;
     }
 
+    @Override
+    public Collection<PropertyRestriction> getPropertyRestrictions() {
+        return propertyRestrictions.values();
+    }
+
     /**
      * Get the restriction for the given property, if any.
      *

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java?rev=1356004&r1=1356003&r2=1356004&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/Filter.java Sun Jul  1 20:22:29 2012
@@ -18,6 +18,8 @@
  */
 package org.apache.jackrabbit.oak.spi;
 
+import java.util.Collection;
+
 import org.apache.jackrabbit.oak.api.CoreValue;
 
 /**
@@ -25,6 +27,8 @@ import org.apache.jackrabbit.oak.api.Cor
  */
 public interface Filter {
 
+    Collection<PropertyRestriction> getPropertyRestrictions();
+
     /**
      * Get the property restriction for the given property, if any.
      *

Modified: jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneEditorTest.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneEditorTest.java?rev=1356004&r1=1356003&r2=1356004&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneEditorTest.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/test/java/org/apache/jackrabbit/oak/plugins/lucene/LuceneEditorTest.java Sun Jul  1 20:22:29 2012
@@ -17,6 +17,8 @@
 package org.apache.jackrabbit.oak.plugins.lucene;
 
 import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
 
 import org.apache.jackrabbit.mk.api.MicroKernel;
 import org.apache.jackrabbit.mk.core.MicroKernelImpl;
@@ -26,33 +28,38 @@ import org.apache.jackrabbit.oak.core.De
 import org.apache.jackrabbit.oak.core.RootImpl;
 import org.apache.jackrabbit.oak.kernel.KernelNodeStore;
 import org.apache.jackrabbit.oak.plugins.memory.MemoryValueFactory;
-import org.apache.lucene.index.IndexReader;
+import org.apache.jackrabbit.oak.query.ast.Operator;
+import org.apache.jackrabbit.oak.query.index.FilterImpl;
+import org.apache.jackrabbit.oak.spi.Cursor;
+import org.apache.jackrabbit.oak.spi.Filter;
+import org.apache.jackrabbit.oak.spi.QueryIndex;
 import org.apache.lucene.store.Directory;
 import org.junit.Test;
 
 public class LuceneEditorTest {
 
-    // @Test
+    @Test
     public void testLucene() throws Exception {
         MicroKernel mk = new MicroKernelImpl();
         KernelNodeStore store = new KernelNodeStore(
                 mk, new LuceneEditor("jcr:system", "oak:lucene"));
         Root root = new RootImpl(store, "");
         Tree tree = root.getTree("/");
-        System.out.println(store.getRoot());
 
         tree.setProperty("foo", MemoryValueFactory.INSTANCE.createValue("bar"));
         root.commit(DefaultConflictHandler.OURS);
 
-        Directory directory = new OakDirectory(
-                store, store.getRoot(), "jcr:system", "oak:lucene");
-        System.out.println(store.getRoot());
-        IndexReader reader = IndexReader.open(directory);
-        try {
-            assertEquals(1, reader.numDocs());
-        } finally {
-            reader.close();
-        }
+        QueryIndex index = new LuceneIndex(store, "jcr:system", "oak:lucene");
+        FilterImpl filter = new FilterImpl(null);
+        filter.restrictPath("/", Filter.PathRestriction.EXACT);
+        filter.restrictProperty(
+                "foo",
+                Operator.EQUAL,
+                MemoryValueFactory.INSTANCE.createValue("bar"));
+        Cursor cursor = index.query(filter, null);
+        assertTrue(cursor.next());
+        assertEquals("/", cursor.currentPath());
+        assertFalse(cursor.next());
     }
 
 }