]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/IndexedRelationsSearcherBase.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.db.indexing / src / org / simantics / db / indexing / IndexedRelationsSearcherBase.java
diff --git a/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/IndexedRelationsSearcherBase.java b/bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/IndexedRelationsSearcherBase.java
new file mode 100644 (file)
index 0000000..f26f9a9
--- /dev/null
@@ -0,0 +1,1074 @@
+/*******************************************************************************\r
+ * Copyright (c) 2007, 2015 Association for Decentralized Information Management\r
+ * in Industry THTH ry.\r
+ * All rights reserved. This program and the accompanying materials\r
+ * are made available under the terms of the Eclipse Public License v1.0\r
+ * which accompanies this distribution, and is available at\r
+ * http://www.eclipse.org/legal/epl-v10.html\r
+ *\r
+ * Contributors:\r
+ *     VTT Technical Research Centre of Finland - initial API and implementation\r
+ *     Semantum Oy - Fix for simantics issue #6053\r
+ *******************************************************************************/\r
+package org.simantics.db.indexing;\r
+\r
+import java.io.File;\r
+import java.io.IOException;\r
+import java.util.ArrayList;\r
+import java.util.Collection;\r
+import java.util.Collections;\r
+import java.util.Iterator;\r
+import java.util.List;\r
+import java.util.Map;\r
+import java.util.concurrent.ExecutorService;\r
+import java.util.concurrent.Executors;\r
+import java.util.concurrent.Semaphore;\r
+import java.util.concurrent.ThreadFactory;\r
+import java.util.concurrent.atomic.AtomicReference;\r
+\r
+import org.apache.lucene.document.Document;\r
+import org.apache.lucene.document.DocumentStoredFieldVisitor;\r
+import org.apache.lucene.document.Field;\r
+import org.apache.lucene.document.FieldType;\r
+import org.apache.lucene.document.LongField;\r
+import org.apache.lucene.document.TextField;\r
+import org.apache.lucene.index.CorruptIndexException;\r
+import org.apache.lucene.index.DirectoryReader;\r
+import org.apache.lucene.index.FieldInfo;\r
+import org.apache.lucene.index.IndexNotFoundException;\r
+import org.apache.lucene.index.IndexReader;\r
+import org.apache.lucene.index.IndexWriter;\r
+import org.apache.lucene.index.IndexWriterConfig;\r
+import org.apache.lucene.index.IndexWriterConfig.OpenMode;\r
+import org.apache.lucene.index.IndexableField;\r
+import org.apache.lucene.index.StoredFieldVisitor;\r
+import org.apache.lucene.index.Term;\r
+import org.apache.lucene.queryparser.classic.ParseException;\r
+import org.apache.lucene.search.IndexSearcher;\r
+import org.apache.lucene.search.MatchAllDocsQuery;\r
+import org.apache.lucene.search.Query;\r
+import org.apache.lucene.search.ScoreDoc;\r
+import org.apache.lucene.search.TermQuery;\r
+import org.apache.lucene.search.TopDocs;\r
+import org.apache.lucene.store.Directory;\r
+import org.apache.lucene.store.FSDirectory;\r
+import org.apache.lucene.util.Version;\r
+import org.eclipse.core.runtime.IProgressMonitor;\r
+import org.eclipse.core.runtime.SubMonitor;\r
+import org.simantics.databoard.util.ObjectUtils;\r
+import org.simantics.db.ReadGraph;\r
+import org.simantics.db.RequestProcessor;\r
+import org.simantics.db.Resource;\r
+import org.simantics.db.Session;\r
+import org.simantics.db.common.request.ReadRequest;\r
+import org.simantics.db.common.request.SafeName;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.exception.DatabaseException;\r
+import org.simantics.db.layer0.adapter.GenericRelation;\r
+import org.simantics.db.request.Read;\r
+import org.simantics.db.service.CollectionSupport;\r
+import org.simantics.db.service.SerialisationSupport;\r
+import org.simantics.utils.DataContainer;\r
+import org.simantics.utils.FileUtils;\r
+import org.simantics.utils.datastructures.Pair;\r
+\r
+import gnu.trove.map.hash.THashMap;\r
+\r
+/**\r
+ * @author Tuukka Lehtonen\r
+ * @author Antti Villberg\r
+ */\r
+abstract public class IndexedRelationsSearcherBase {\r
+\r
+    protected enum State {\r
+        // No index is available\r
+        NONE, \r
+        // An index is available, but there is a problem with it\r
+        PROBLEM, \r
+        // An index is available but no reader or writer is ready\r
+        READY,\r
+        // A reader is ready\r
+        READ, \r
+        // A writer (and a reader) is ready\r
+        WRITE\r
+    }\r
+    \r
+    private State state = State.READY;\r
+    private Throwable exception;\r
+    \r
+    public Throwable getException() {\r
+       return exception;\r
+    }\r
+\r
+    public void setProblem(Throwable t) {\r
+       this.state = State.PROBLEM;\r
+       this.exception = t;\r
+    }\r
+    \r
+    public void setNone() {\r
+       this.state = State.NONE;\r
+    }\r
+    \r
+    public void setReady() {\r
+       this.state = State.READY;\r
+    }\r
+    \r
+    protected boolean checkState(State state) {\r
+       return this.state == state;\r
+    }\r
+    \r
+    protected void assertState(State state) throws AssertionError {\r
+\r
+       if(this.state != state) throw new AssertionError("Illegal state, expected " + state.name() + " but was in " + this.state.name());\r
+       \r
+    }\r
+    \r
+    public void changeState(IProgressMonitor monitor, Session session, State state) {\r
+       changeState(monitor, session, state, 0);\r
+    }\r
+\r
+    protected void changeState(IProgressMonitor monitor, Session session, State state, int depth) {\r
+\r
+       if(this.state == state) return;\r
+\r
+       if (IndexPolicy.TRACE_INDEX_MANAGEMENT)\r
+               System.err.println("Index state " + this.state.name() + " => " + state.name() + " " + this);\r
+\r
+       // Check transitions\r
+       \r
+       // Try to exit problem state\r
+       if (State.PROBLEM == this.state && depth > 0) {\r
+               Throwable t = bestEffortClear(monitor, session);\r
+               if(t != null) {\r
+                               exception = t;\r
+                               return;\r
+               }\r
+               // Managed to get into initial state\r
+               this.state = State.NONE;\r
+               return;\r
+       }\r
+\r
+       // Cannot move into read from no index\r
+       if (State.NONE ==  this.state && State.READ == state) return;\r
+       // Cannot move into write from no index\r
+       if (State.NONE ==  this.state && State.WRITE == state) return;\r
+       \r
+               boolean success = false;\r
+\r
+       try {\r
+\r
+               if (searcher != null) {\r
+                       searcher = null;\r
+               }\r
+               if (reader != null) {\r
+                       reader.close();\r
+                       reader = null;\r
+               }\r
+                       closeWriter(writer);\r
+                       directory = null;\r
+                       \r
+                       success = true;\r
+\r
+               // Enter new state\r
+               if (State.READ == state || State.WRITE == state) {\r
+                       \r
+                       success = false;\r
+                       \r
+                       boolean forWriting = State.WRITE == state;\r
+\r
+                       if (directory != null)\r
+                               throw new IllegalStateException(getDescriptor() + "Index already loaded");\r
+\r
+                       SubMonitor mon = SubMonitor.convert(monitor, 100);\r
+\r
+                       mon.beginTask("Loading index", 100);\r
+\r
+                       if (IndexPolicy.TRACE_INDEX_LOAD)\r
+                    System.out.println(getDescriptor() + "Loading Lucene index from " + indexPath + " for " + (forWriting ? "writing" : "reading"));\r
+\r
+                long start = System.nanoTime();\r
+\r
+                directory = getDirectory(session);\r
+\r
+                if (forWriting) {\r
+                    // Never overwrite an index that is about to be loaded.\r
+                    // TODO: could use OpenMode.CREATE_OR_APPEND but must test first\r
+                    IndexWriterConfig config = new IndexWriterConfig(Version.LUCENE_4_9, Queries.getAnalyzer()).setOpenMode(OpenMode.APPEND);\r
+                    try {\r
+                        // FIXME: platform #4676\r
+                        writer = new IndexWriter(directory, config);\r
+                    } catch (IndexNotFoundException e) {\r
+                        // There was no pre-existing index on disk. Create it now.\r
+                        writer = new IndexWriter(directory, config.setOpenMode(OpenMode.CREATE));\r
+                        writer.commit();\r
+                    }\r
+                    reader = DirectoryReader.open(directory);\r
+                    searcher = new IndexSearcher(reader);\r
+                } else {\r
+                    reader = DirectoryReader.open(directory);\r
+                    searcher = new IndexSearcher(reader);\r
+                }\r
+\r
+                long end = System.nanoTime();\r
+\r
+                mon.worked(100);\r
+\r
+                if (IndexPolicy.PERF_INDEX_LOAD) {\r
+                    double time = (end - start) * 1e-6;\r
+                    System.out.println(getDescriptor() + "Loaded Lucene index from " + indexPath + " for " + (forWriting ? "writing" : "reading") + " in " + time + " ms");\r
+                }\r
+\r
+                success = true;\r
+                \r
+               }\r
+                \r
+       } catch (Throwable t) {\r
+\r
+               setProblem(t);\r
+\r
+       } finally {\r
+\r
+               if(!success) {\r
+                       this.state = State.PROBLEM;\r
+                       changeState(monitor, session, State.NONE, depth+1);\r
+                       return;\r
+               }\r
+\r
+       }\r
+\r
+       this.state = state;\r
+       \r
+    }\r
+\r
+    public static final FieldType STRING_TYPE = new FieldType();\r
+\r
+    static {\r
+      STRING_TYPE.setIndexed(true);\r
+      STRING_TYPE.setStored(true);\r
+      STRING_TYPE.setTokenized(true);\r
+      STRING_TYPE.freeze();\r
+    }\r
+\r
+    protected static Field makeField(String fieldName, String fieldClass) throws DatabaseException {\r
+        switch (fieldClass) {\r
+        case "Long":   return new LongField(fieldName, 0L, Field.Store.YES);\r
+        case "String": return new Field    (fieldName, "", STRING_TYPE);\r
+        case "Text":   return new TextField(fieldName, "", Field.Store.YES);\r
+        default:\r
+            throw new DatabaseException("Can only index Long, String and Text fields, encountered field type " + fieldClass);\r
+        }\r
+    }\r
+\r
+    protected static Field[] makeFieldsForRelation(GenericRelation r, int boundLength, Document document) throws DatabaseException {\r
+        Pair<String, String>[] fields = r.getFields();\r
+        Field[] fs = new Field[Math.max(0, fields.length - boundLength)];\r
+        for (int i = boundLength; i < fields.length; i++) {\r
+            Field f = makeField(fields[i].first, fields[i].second);\r
+            fs[i - boundLength] = f;\r
+            if (document != null)\r
+                document.add(f);\r
+        }\r
+        return fs;\r
+    }\r
+\r
+    void insertIndex(IProgressMonitor monitor, GenericRelation r, int boundLength, Collection<Object[]> documentsData)\r
+    throws CorruptIndexException, IOException, DatabaseException {\r
+        assertAccessOpen(true);\r
+\r
+        if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+            System.out.println(getDescriptor() + "Inserting " + documentsData.size() + " documents into index at " + indexPath);\r
+\r
+        long start = 0, end = 0;\r
+        if (IndexPolicy.PERF_INDEX_UPDATE)\r
+            start = System.nanoTime();\r
+\r
+        try {\r
+            Document document = new Document();\r
+            Field[] fs = makeFieldsForRelation(r, boundLength, document);\r
+\r
+            for (Object[] documentData : documentsData) {\r
+                for (int i = 0; i < documentData.length; i++) {\r
+                    Object value = documentData[i];\r
+                    if (value instanceof String) {\r
+                        fs[i].setStringValue((String) value);\r
+                    } else if (value instanceof Long) {\r
+                        fs[i].setLongValue((Long) value);\r
+                    } else {\r
+                        System.err.println("Can only index Long and String fields, encountered " + value);\r
+                        // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
+                        continue;\r
+                    }\r
+//                    System.out.println("index " + fs[i].name() + " = " + result[i]);\r
+                }\r
+\r
+                if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                    System.out.println(getDescriptor() + "Inserting document " + document);\r
+\r
+                writer.addDocument(document);\r
+            }\r
+\r
+            if (IndexPolicy.PERF_INDEX_UPDATE) {\r
+                end = System.nanoTime();\r
+                double ms = (end - start) * 1e-6;\r
+                System.out.println(getDescriptor() + "Inserted " + documentsData.size() + " documents into index at " + indexPath + " in " + ms + " ms");\r
+            }\r
+\r
+        } finally {\r
+        }\r
+    }\r
+\r
+    void removeIndex(IProgressMonitor monitor, GenericRelation r, RequestProcessor processor, String key, Collection<Object> keyValues) throws DatabaseException, CorruptIndexException, IOException {\r
+        assertAccessOpen(true);\r
+\r
+        if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+            System.out.println(getDescriptor() + "Removing " + keyValues.size() + " documents from index at " + indexPath);\r
+\r
+        long start = 0, end = 0;\r
+        if (IndexPolicy.PERF_INDEX_UPDATE)\r
+            start = System.nanoTime();\r
+\r
+        try {\r
+            for (Object keyValue : keyValues) {\r
+                Term removedTerm = null;\r
+                if (keyValue instanceof Long) {\r
+                    removedTerm = IndexUtils.longTerm(key, (Long) keyValue);\r
+                } else if (keyValue instanceof String) {\r
+                    removedTerm = new Term(key, (String) keyValue);\r
+                } else {\r
+                    // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
+                    continue;\r
+                }\r
+\r
+                if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                    System.out.println(getDescriptor() + "Removing document with key " + removedTerm);\r
+                writer.deleteDocuments(removedTerm);\r
+            }\r
+\r
+            if (IndexPolicy.PERF_INDEX_UPDATE) {\r
+                end = System.nanoTime();\r
+                double ms = (end - start) * 1e-6;\r
+                System.out.println(getDescriptor() + "Removed " + keyValues.size() + " documents from index at " + indexPath + " in " + ms + " ms");\r
+            }\r
+\r
+        } finally {\r
+        }\r
+    }\r
+\r
+    void removeIndex(IProgressMonitor monitor) throws DatabaseException, CorruptIndexException, IOException {\r
+        assertAccessOpen(true);\r
+\r
+        long start = 0, end = 0;\r
+        if (IndexPolicy.PERF_INDEX_UPDATE)\r
+            start = System.nanoTime();\r
+\r
+        try {\r
+\r
+            writer.deleteAll();\r
+\r
+            if (IndexPolicy.PERF_INDEX_UPDATE) {\r
+                end = System.nanoTime();\r
+                double ms = (end - start) * 1e-6;\r
+                System.out.println(getDescriptor() + "Removed all documents from index at " + indexPath + " in " + ms + " ms");\r
+            }\r
+\r
+        } finally {\r
+        }\r
+    }\r
+    \r
+    boolean replaceIndex(IProgressMonitor monitor, String key, Collection<Object> keyValues, GenericRelation r, int boundLength, Collection<Object[]> documentsData) throws CorruptIndexException, IOException, DatabaseException {\r
+\r
+       boolean didReplace = false;\r
+       \r
+       assertAccessOpen(true);\r
+        if (keyValues.size() != documentsData.size())\r
+            throw new IllegalArgumentException("keyValues size does not match documents data size, " + keyValues.size() + " <> " + documentsData.size());\r
+\r
+        if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+            System.out.println(getDescriptor() + "Replacing " + keyValues.size() + " documents from index at " + indexPath);\r
+\r
+        long start = 0, end = 0;\r
+        if (IndexPolicy.PERF_INDEX_UPDATE)\r
+            start = System.nanoTime();\r
+\r
+        try {\r
+            Iterator<Object> keyIt = keyValues.iterator();\r
+            Iterator<Object[]> documentDataIt = documentsData.iterator();\r
+\r
+            Document document = new Document();\r
+            Field[] fs = makeFieldsForRelation(r, boundLength, document);\r
+\r
+            nextDocument:\r
+                while (keyIt.hasNext()) {\r
+                    Object keyValue = keyIt.next();\r
+                    Object[] documentData = documentDataIt.next();\r
+\r
+                    Term removedTerm = null;\r
+                    if (keyValue instanceof Long) {\r
+                        removedTerm = IndexUtils.longTerm(key, (Long) keyValue);\r
+                    } else if (keyValue instanceof String) {\r
+                        removedTerm = new Term(key, (String) keyValue);\r
+                    } else {\r
+                        // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
+                        System.err.println("[" + getClass().getSimpleName() + "] Unrecognized document key to remove '" + keyValue + "', only " + String.class + " and " + Resource.class + " are supported.");\r
+                        continue nextDocument;\r
+                    }\r
+\r
+                    for (int i = 0; i < documentData.length; i++) {\r
+                        Object value = documentData[i];\r
+                        if (value instanceof String) {\r
+                            fs[i].setStringValue((String) value);\r
+                        } else if (keyValue instanceof Long) {\r
+                            fs[i].setLongValue((Long)value);\r
+                        } else {\r
+                            // FIXME: should throw an exception for illegal input data but this would leave the index in an incoherent state\r
+                            System.err.println("[" + getClass().getSimpleName() + "] Unrecognized document value '" + value + "' for field '" + fs[i].toString() + "', only " + String.class + " and " + Resource.class + " are supported.");\r
+                            continue nextDocument;\r
+                        }\r
+                    }\r
+\r
+                    if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                        System.out.println(getDescriptor() + "Replacing document with key " + removedTerm + " with " + document);\r
+\r
+                    boolean done = false;\r
+                    if(requireChangeInfoOnReplace()) {\r
+                           TopDocs exist = searcher.search(new TermQuery(removedTerm), null, 2);\r
+                           if(exist.scoreDocs.length == 1 && requireChangeInfoOnReplace()) {\r
+                               Document doc = reader.document(exist.scoreDocs[0].doc);\r
+                               if(!areSame(doc, document)) {\r
+                                   writer.deleteDocuments(removedTerm);\r
+                                   writer.addDocument(document);\r
+                                   didReplace |= true;\r
+                                   if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                                       System.out.println("-replaced single existing");\r
+                               } else {\r
+                                   if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                                       System.out.println("-was actually same than single existing");\r
+                               }\r
+                               done = true;\r
+                           } \r
+                    }\r
+                    if(!done) {\r
+                       writer.deleteDocuments(removedTerm);\r
+                       writer.addDocument(document);\r
+                       didReplace |= true;\r
+                       if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+                               System.out.println("-had many or none - removed all existing");\r
+                    }\r
+                    \r
+                }\r
+\r
+            if (IndexPolicy.PERF_INDEX_UPDATE) {\r
+                end = System.nanoTime();\r
+                double ms = (end - start) * 1e-6;\r
+                System.out.println(getDescriptor() + "Replaced " + keyValues.size() + " documents from index at " + indexPath + " in " + ms + " ms");\r
+            }\r
+\r
+        } finally {\r
+        }\r
+        \r
+        return didReplace;\r
+        \r
+    }\r
+    \r
+    protected boolean requireChangeInfoOnReplace() {\r
+       return true;\r
+    }\r
+    \r
+    private boolean areSame(Document d1, Document d2) {\r
+       List<IndexableField> fs1 = d1.getFields();\r
+       List<IndexableField> fs2 = d2.getFields();\r
+       if(fs1.size() != fs2.size()) return false;\r
+       for(int i=0;i<fs1.size();i++) {\r
+               IndexableField f1 = fs1.get(i);\r
+               IndexableField f2 = fs2.get(i);\r
+               String s1 = f1.stringValue();\r
+               String s2 = f2.stringValue();\r
+            if (IndexPolicy.TRACE_INDEX_UPDATE)\r
+               System.err.println("areSame " + s1 + " vs. " + s2 );\r
+               if(!ObjectUtils.objectEquals(s1,s2)) return false;\r
+       }\r
+       return true;\r
+    }\r
+\r
+    final RequestProcessor session;\r
+\r
+    final Resource         relation;\r
+\r
+    /**\r
+     * The schema of the index, i.e. the fields that will be indexed per\r
+     * document for the specified relation. Since the relation stays the same\r
+     * throughout the lifetime of this class, the index schema is also assumed\r
+     * to the same. This means that {@link GenericRelation#getFields()} is\r
+     * assumed to stay the same.\r
+     */\r
+    final IndexSchema      schema;\r
+\r
+    Resource         input;\r
+\r
+    File             indexPath;\r
+\r
+    Directory        directory;\r
+\r
+    IndexReader      reader;\r
+\r
+    IndexWriter      writer;\r
+\r
+    IndexSearcher    searcher;\r
+\r
+    IndexedRelationsSearcherBase(RequestProcessor session, Resource relation, Resource input) {\r
+        this.session = session;\r
+        this.relation = relation;\r
+        this.input = input;\r
+        this.indexPath = getIndexDirectory(session.getSession(), relation, input);\r
+        if(isIndexAvailable()) {\r
+               state = State.READY;\r
+        } else {\r
+               state = State.NONE;\r
+        }\r
+        this.schema = IndexSchema.readFromRelation(session, relation);\r
+    }\r
+\r
+    Directory getDirectory(Session session) throws IOException {\r
+        return FSDirectory.open(indexPath);\r
+    }\r
+\r
+    abstract String getDescriptor();\r
+    \r
+    /**\r
+     * Ensures that searcher is in read or write state.\r
+     * \r
+     * @param forWriting <code>true</code> to open index for writing,\r
+     *        <code>false</code> for reading\r
+     * @return true is required state was reached       \r
+     *        \r
+     */\r
+    boolean startAccess(IProgressMonitor monitor, Session session, boolean forWriting) {\r
+       if(forWriting) {\r
+               changeState(monitor, session, State.WRITE);\r
+               return checkState(State.WRITE);\r
+       } else {\r
+               changeState(monitor, session, State.READ);\r
+               return checkState(State.READ);\r
+       }\r
+    }\r
+\r
+    boolean hasAccess(boolean forWriting) {\r
+       \r
+        if (forWriting)\r
+               return checkState(State.WRITE); \r
+        else\r
+               return checkState(State.WRITE) || checkState(State.READ);\r
+       \r
+    }\r
+    \r
+    void assertAccessOpen(boolean forWriting) {\r
+        if (forWriting)\r
+               if(!checkState(State.WRITE)) \r
+                throw new IllegalStateException("index not opened for writing (directory=" + directory + ", reader=" + reader + ")");\r
+        else\r
+               if(!(checkState(State.WRITE) || checkState(State.READ))) \r
+                throw new IllegalStateException("index not opened for reading (directory=" + directory + ", writer=" + writer + ")");\r
+    }\r
+    \r
+    void closeWriter(IndexWriter writer) throws CorruptIndexException, IOException {\r
+        if (writer == null)\r
+            return;\r
+\r
+        try {\r
+            // May throw OOME, see IndexWriter javadoc for the correct actions.\r
+            writer.close(false);\r
+        } catch (OutOfMemoryError e) {\r
+            writer.close();\r
+            throw e;\r
+        }\r
+    }\r
+\r
+    private static String getPattern(GenericRelation relation, int boundCount) {\r
+        String result = "";\r
+        for (int i = 0; i < boundCount; i++)\r
+            result += "b";\r
+        for (int i = 0; i < relation.getFields().length - boundCount; i++)\r
+            result += "f";\r
+        return result;\r
+    }\r
+    \r
+    private static final int INDEXING_THREAD_COUNT = 2;\r
+    \r
+    private static final ExecutorService executor = Executors.newFixedThreadPool(INDEXING_THREAD_COUNT, new ThreadFactory() {\r
+        @Override\r
+        public Thread newThread(Runnable r) {\r
+            Thread t = new Thread(r, "Lucene Index Creator");\r
+            if (t.isDaemon())\r
+                t.setDaemon(true);\r
+            if (t.getPriority() != Thread.NORM_PRIORITY)\r
+                t.setPriority(Thread.NORM_PRIORITY);\r
+            return t;\r
+        }\r
+    });\r
+\r
+    void initializeIndex(IProgressMonitor monitor, ReadGraph graph, final Object[] bound, boolean overwrite) throws IOException,\r
+    DatabaseException {\r
+\r
+        final SubMonitor mon = SubMonitor.convert(monitor, 100);\r
+\r
+        if (IndexPolicy.TRACE_INDEX_INIT)\r
+            System.out.println(getDescriptor() + "Initializing index at " + indexPath + " (overwrite = " + overwrite + ")");\r
+        mon.beginTask("Initializing Index", 100);\r
+\r
+        if (overwrite) {\r
+            mon.subTask("Erasing previous index");\r
+            FileUtils.deleteAll(indexPath);\r
+        }\r
+\r
+        final AtomicReference<FSDirectory> directory = new AtomicReference<FSDirectory>();\r
+        final AtomicReference<IndexWriter> writer = new AtomicReference<IndexWriter>();\r
+\r
+        try {\r
+            mon.subTask("Create index at " + indexPath.toString());\r
+            createDirectory(indexPath);\r
+\r
+            directory.set(FSDirectory.open(indexPath));\r
+            IndexWriterConfig conf = new IndexWriterConfig(Version.LUCENE_4_9, Queries.getAnalyzer()).setOpenMode(OpenMode.CREATE);\r
+            writer.set(new IndexWriter(directory.get(), conf));\r
+\r
+            mon.worked(5);\r
+\r
+            final DataContainer<Long> start = new DataContainer<Long>();\r
+\r
+            graph.syncRequest(new ReadRequest() {\r
+\r
+                @Override\r
+                public void run(ReadGraph graph) throws DatabaseException {\r
+\r
+                    final GenericRelation r = graph.adapt(relation, GenericRelation.class);\r
+                    if (r == null)\r
+                        throw new DatabaseException("Given resource " + graph.syncRequest(new SafeName(relation))\r
+                                + "could not be adapted to GenericRelation.");\r
+\r
+                    mon.worked(45);\r
+\r
+                    GenericRelation selection = r.select(getPattern(r, bound.length), bound);\r
+                    \r
+                    long perfStart = 0;\r
+                    if (IndexPolicy.PERF_INDEX_INIT)\r
+                        perfStart = System.nanoTime();\r
+\r
+                    final List<Object[]> results = selection.realize(graph);\r
+\r
+                    if (IndexPolicy.PERF_INDEX_INIT)\r
+                        System.out.println(getDescriptor() + "Realized index at " + indexPath + " in " + (1e-9 * (System.nanoTime()-perfStart)) + " seconds.");\r
+                    \r
+                    if (IndexPolicy.TRACE_INDEX_INIT)\r
+                        System.out.println(getDescriptor() + "Indexed relation " + r + " produced " + results.size() + " results");\r
+\r
+                    if (IndexPolicy.PERF_INDEX_INIT)\r
+                        start.set(System.nanoTime());\r
+\r
+                    final Semaphore s = new Semaphore(0);\r
+\r
+                    for(int i=0;i<INDEXING_THREAD_COUNT;i++) {\r
+                       \r
+                       final int startIndex = i;\r
+                       \r
+                       executor.submit(new Runnable() {\r
+\r
+                                                       @Override\r
+                                                       public void run() {\r
+                                                               \r
+                                                               try {\r
+                                                                       \r
+                                                   final Document document = new Document();\r
+\r
+                                                                       Field[] fs = makeFieldsForRelation(r, bound.length, document);\r
+\r
+                                                                       for (int index = startIndex; index < results.size(); index+=INDEXING_THREAD_COUNT) {\r
+                                                                               Object[] result = results.get(index);\r
+                                                                               for (int i = 0; i < result.length; i++) {\r
+                                                                                       Object value = result[i];\r
+                                                                                       if (value instanceof String) {\r
+                                                                                               if (IndexPolicy.DEBUG_INDEX_INIT)\r
+                                                                                                       System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : String");\r
+                                                                                               fs[i].setStringValue((String) value);\r
+                                                                                       } else if (value instanceof Long) {\r
+                                                                                               if (IndexPolicy.DEBUG_INDEX_INIT)\r
+                                                                                                       System.out.println(getDescriptor() + "index " + fs[i].name() + " = " + value + " : Long");\r
+                                                                                               fs[i].setLongValue((Long) value);\r
+                                                                                       }\r
+                                                                               }\r
+                                                                               try {\r
+                                                                                       writer.get().addDocument(document);\r
+                                                                               } catch (CorruptIndexException e) {\r
+                                                                                       throw new IllegalStateException(e);\r
+                                                                               } catch (IOException e) {\r
+                                                                                       throw new IllegalStateException(e);\r
+                                                                               } finally {\r
+                                                                                       mon.worked(1);\r
+                                                                               }\r
+                                                                       }\r
+\r
+                                                                       s.release();\r
+\r
+                                                               } catch (DatabaseException e) {\r
+                                                                       throw new IllegalStateException(e);\r
+                                                               }\r
+                                                               \r
+                                                       }\r
+                               \r
+                       });\r
+                       \r
+                    }\r
+                    \r
+                    try {\r
+                                               s.acquire(INDEXING_THREAD_COUNT);\r
+                                       } catch (InterruptedException e) {\r
+                                               e.printStackTrace();\r
+                                       }\r
+                    \r
+                }\r
+                \r
+            });\r
+\r
+            // http://www.gossamer-threads.com/lists/lucene/java-dev/47895\r
+            // and http://lucene.apache.org/java/docs/index.html#27+November+2011+-+Lucene+Core+3.5.0\r
+            // advise against calling optimize at all. So let's not do it anymore.\r
+            //writer.get().optimize();\r
+            //writer.get().commit();\r
+\r
+            if (IndexPolicy.PERF_INDEX_INIT) {\r
+                long end = System.nanoTime();\r
+                System.out.println(getDescriptor() + "Wrote index at " + indexPath + " in " + (1e-9 * (end-start.get())) + " seconds.");\r
+            }\r
+\r
+        } catch (DatabaseException e) {\r
+            \r
+            Logger.defaultLogError(e);\r
+            \r
+        } finally {\r
+            try {\r
+                closeWriter(writer.getAndSet(null));\r
+            } finally {\r
+                directory.getAndSet(null).close();\r
+            }\r
+        }\r
+    }\r
+\r
+    \r
+    public List<Object[]> debugDocs(IProgressMonitor monitor) throws ParseException, IOException, DatabaseException {\r
+    \r
+           Query query = new MatchAllDocsQuery(); \r
+       \r
+           TopDocs td = searcher.search(query, Integer.MAX_VALUE);\r
+    \r
+           ScoreDoc[ ] scoreDocs = td.scoreDocs; \r
+           List<Object[]> result = new ArrayList<Object[]>(scoreDocs.length);\r
+       \r
+           for(ScoreDoc scoreDoc:scoreDocs) {\r
+       \r
+               try {\r
+       \r
+                   Document doc = reader.document(scoreDoc.doc);\r
+                   List<IndexableField> fs = doc.getFields();\r
+                   Object[] o = new Object[fs.size()];\r
+                   int index = 0; \r
+                   for (IndexableField f : fs) {\r
+                    o[index++] = f.stringValue();\r
+                   }\r
+                   result.add(o);\r
+       \r
+               } catch (CorruptIndexException e) {\r
+                   throw new DatabaseException(e);\r
+               } catch (IOException e) {\r
+                   throw new DatabaseException(e);\r
+               }\r
+\r
+           }\r
+           \r
+           return result;\r
+           \r
+    }\r
+\r
+    \r
+    List<Map<String, Object>> doSearch(IProgressMonitor monitor, RequestProcessor processor, String search, int maxResultCount) throws ParseException, IOException,\r
+    DatabaseException {\r
+\r
+        // An empty search string will crash QueryParser\r
+        // Just return no results for empty queries.\r
+        //System.out.println("search: '" + search + "'");\r
+        if (search.isEmpty())\r
+            return Collections.emptyList();\r
+\r
+        assertAccessOpen(false);\r
+\r
+        Query query = Queries.parse(search, schema);\r
+\r
+        long start = System.nanoTime();\r
+\r
+        maxResultCount = Math.min(maxResultCount, searcher.getIndexReader().numDocs());\r
+        if (maxResultCount == 0)\r
+            return Collections.emptyList();\r
+        \r
+        final TopDocs docs = searcher.search(query, null, maxResultCount);\r
+        \r
+//        for(Object[] o : debugDocs(monitor)) {\r
+//            System.err.println("-" + Arrays.toString(o));\r
+//        }\r
+        \r
+        if (IndexPolicy.PERF_INDEX_QUERY) {\r
+            long end = System.nanoTime();\r
+            System.out.println(getDescriptor() + "search(" + search + ", " + maxResultCount + ") into index at " + indexPath + " took " + (1e-9 * (end-start)) + " seconds.");\r
+        }\r
+\r
+        if (docs.totalHits == 0) {\r
+            return Collections.emptyList();\r
+        }\r
+\r
+        return processor.syncRequest(new Read<List<Map<String, Object>>>() {\r
+\r
+            @Override\r
+            public List<Map<String, Object>> perform(ReadGraph graph) throws DatabaseException {\r
+\r
+                GenericRelation r = graph.adapt(relation, GenericRelation.class);\r
+                if (r == null)\r
+                    throw new DatabaseException("Given resource " + graph.syncRequest(new SafeName(relation))\r
+                            + "could not be adapted to GenericRelation.");\r
+\r
+                SerialisationSupport support = graph.getService(SerialisationSupport.class);\r
+\r
+                List<Map<String, Object>> result = new ArrayList<Map<String, Object>>(docs.scoreDocs.length);\r
+                \r
+                final DocumentStoredFieldVisitor visitor = new DocumentStoredFieldVisitor();\r
+                \r
+                for (ScoreDoc scoreDoc : docs.scoreDocs) {\r
+\r
+                    try {\r
+\r
+                        reader.document(scoreDoc.doc, visitor);\r
+                        \r
+                        Document doc = visitor.getDocument();\r
+\r
+                        List<IndexableField> fs = doc.getFields();\r
+                        Map<String, Object> entry = new THashMap<String, Object>(fs.size());\r
+                        for (IndexableField f : fs) {\r
+                            IndexSchema.Type type = schema.typeMap.get(f.name());\r
+                            if (type == IndexSchema.Type.LONG) {\r
+                                entry.put(f.name(), support.getResource(f.numericValue().longValue()));\r
+                            } else {\r
+                                entry.put(f.name(), f.stringValue());\r
+                            }\r
+                        }\r
+                        \r
+                        result.add(entry);\r
+\r
+                    } catch (CorruptIndexException e) {\r
+                        throw new DatabaseException(e);\r
+                    } catch (IOException e) {\r
+                        throw new DatabaseException(e);\r
+                    }\r
+\r
+                }\r
+\r
+                return result;\r
+\r
+            }\r
+        });\r
+    }\r
+\r
+    static class ResourceVisitor extends StoredFieldVisitor {\r
+       \r
+       public long id;\r
+\r
+               @Override\r
+               public Status needsField(FieldInfo fieldInfo) throws IOException {\r
+                       if("Resource".equals(fieldInfo.name)) return Status.YES;\r
+                       return Status.NO;\r
+               }\r
+               \r
+               @Override\r
+               public void longField(FieldInfo fieldInfo, long value) throws IOException {\r
+                       id = value;\r
+               }\r
+       \r
+    };\r
+    \r
+    static class DumpVisitor extends StoredFieldVisitor {\r
+\r
+       public List<Object> values;\r
+       \r
+       DumpVisitor(List<Object> values) {\r
+               this.values = values;\r
+       }\r
+\r
+               @Override\r
+               public Status needsField(FieldInfo fieldInfo) throws IOException {\r
+                       return Status.YES;\r
+               }\r
+               \r
+               @Override\r
+               public void longField(FieldInfo fieldInfo, long value) throws IOException {\r
+                       values.add(value);\r
+               }\r
+               \r
+               @Override\r
+               public void stringField(FieldInfo fieldInfo, String value) throws IOException {\r
+                       values.add(value);\r
+               }\r
+\r
+    }\r
+\r
+    List<Resource> doSearchResources(IProgressMonitor monitor, RequestProcessor processor, String search, int maxResultCount) throws ParseException, IOException,\r
+    DatabaseException {\r
+\r
+        // An empty search string will crash QueryParser\r
+        // Just return no results for empty queries.\r
+        //System.out.println("search: '" + search + "'");\r
+        if (search.isEmpty())\r
+            return Collections.emptyList();\r
+\r
+        assertAccessOpen(false);\r
+\r
+        Query query = Queries.parse(search, schema);\r
+\r
+        long start = System.nanoTime();\r
+\r
+        maxResultCount = Math.min(maxResultCount, searcher.getIndexReader().numDocs());\r
+        if (maxResultCount == 0)\r
+            return Collections.emptyList();\r
+        \r
+        final TopDocs docs = searcher.search(query, null, maxResultCount);\r
+        \r
+//        for(Object[] o : debugDocs(monitor)) {\r
+//            System.err.println("-" + Arrays.toString(o));\r
+//        }\r
+        \r
+        if (IndexPolicy.PERF_INDEX_QUERY) {\r
+            long end = System.nanoTime();\r
+            System.out.println(getDescriptor() + "search(" + search + ", " + maxResultCount + ") into index at " + indexPath + " took " + (1e-9 * (end-start)) + " seconds.");\r
+        }\r
+\r
+        if (docs.totalHits == 0) {\r
+            return Collections.emptyList();\r
+        }\r
+        \r
+        return processor.syncRequest(new Read<List<Resource>>() {\r
+\r
+            @Override\r
+            public List<Resource> perform(ReadGraph graph) throws DatabaseException {\r
+\r
+               CollectionSupport cs = graph.getService(CollectionSupport.class);\r
+                SerialisationSupport support = graph.getService(SerialisationSupport.class);\r
+                \r
+               List<Resource> result = cs.createList();\r
+                \r
+               ResourceVisitor visitor = new ResourceVisitor();\r
+                \r
+                for (ScoreDoc scoreDoc : docs.scoreDocs) {\r
+\r
+                    try {\r
+                       \r
+                       reader.document(scoreDoc.doc, visitor);\r
+                       result.add(support.getResource(visitor.id));\r
+\r
+                    } catch (CorruptIndexException e) {\r
+                        throw new DatabaseException(e);\r
+                    } catch (IOException e) {\r
+                        throw new DatabaseException(e);\r
+                    }\r
+\r
+                }\r
+\r
+                return result;\r
+\r
+            }\r
+        });\r
+    }\r
+\r
+    List<Object> doList(IProgressMonitor monitor, RequestProcessor processor) throws ParseException, IOException,\r
+    DatabaseException {\r
+\r
+        assertAccessOpen(false);\r
+\r
+        Query query = new MatchAllDocsQuery(); \r
+\r
+        final TopDocs docs = searcher.search(query, Integer.MAX_VALUE);\r
+        \r
+        ArrayList<Object> result = new ArrayList<Object>();\r
+        \r
+        DumpVisitor visitor = new DumpVisitor(result);\r
+                \r
+        for (ScoreDoc scoreDoc : docs.scoreDocs) {\r
+\r
+               try {\r
+\r
+                       reader.document(scoreDoc.doc, visitor);\r
+\r
+               } catch (CorruptIndexException e) {\r
+                       throw new DatabaseException(e);\r
+               } catch (IOException e) {\r
+                       throw new DatabaseException(e);\r
+               }\r
+\r
+        }\r
+\r
+        return result;\r
+\r
+    }\r
+    \r
+    protected static File getIndexDirectory(Session session, Resource relation, Resource input) {\r
+        File path = DatabaseIndexing.getIndexLocation(session, relation, input);\r
+//        System.out.println("getIndexDirectory = " + path);\r
+        return path;\r
+    }\r
+\r
+    private static void createDirectory(File path) throws IOException {\r
+        if (path.exists() && !path.isDirectory())\r
+            throw new IOException("Could not create index directory " + path + ", a file by that name already exists");\r
+        path.mkdirs();\r
+        if (!path.exists())\r
+            throw new IOException("Could not create index directory " + path + " for an unknown reason");\r
+        if (!path.isDirectory())\r
+            throw new IOException("Could not create index directory " + path + ", a file by that name already exists");\r
+    }\r
+\r
+    File getIndexPath() {\r
+        return indexPath;\r
+    }\r
+\r
+    boolean isIndexAvailable() {\r
+        return (indexPath.exists() && indexPath.isDirectory());\r
+    }\r
+    \r
+    Throwable bestEffortClear(IProgressMonitor monitor, Session session) {\r
+       return null;\r
+    }\r
+\r
+    /*\r
+     * Start from scratch. Clear all caches and rebuild the index. \r
+     */\r
+    Throwable clearDirectory(IProgressMonitor monitor, Session session) {\r
+        \r
+               File file = getIndexPath();\r
+\r
+       try {\r
+\r
+               for(int i=0;i<15;i++) {\r
+                       FileUtils.deleteDir(file);\r
+                       if(!file.exists()) {\r
+                               return null;\r
+                       }\r
+                       try {\r
+                               Thread.sleep(i*100);\r
+                       } catch (InterruptedException e) {\r
+                       }\r
+               }\r
+\r
+       } catch (Throwable t) {\r
+\r
+               return t;\r
+               \r
+       }\r
+\r
+       return new IllegalStateException("Failed to delete directory " + file.getAbsolutePath());\r
+\r
+    }\r
+\r
+}\r