]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics.db.indexing/src/org/simantics/db/indexing/DatabaseIndexing.java
Improve startup time for fresh or rollback'd session in index writing
[simantics/platform.git] / bundles / org.simantics.db.indexing / src / org / simantics / db / indexing / DatabaseIndexing.java
index 8b74ebe792eae52e87d2e2b4af35ff276548e2be..f8a19504c8dc0165f38ddb34f27b7b31fe8a38ed 100644 (file)
-/*******************************************************************************\r
- * Copyright (c) 2007, 2010 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
- *******************************************************************************/\r
-package org.simantics.db.indexing;\r
-\r
-import java.io.File;\r
-import java.io.FileFilter;\r
-import java.io.IOException;\r
-\r
-import org.simantics.db.Resource;\r
-import org.simantics.db.Session;\r
-import org.simantics.db.WriteGraph;\r
-import org.simantics.db.common.request.IndexRoot;\r
-import org.simantics.db.common.request.WriteRequest;\r
-import org.simantics.db.common.utils.Logger;\r
-import org.simantics.db.exception.DatabaseException;\r
-import org.simantics.db.layer0.adapter.GenericRelationIndex;\r
-import org.simantics.db.layer0.genericrelation.IndexedRelations;\r
-import org.simantics.db.layer0.internal.SimanticsInternal;\r
-import org.simantics.db.service.ServerInformation;\r
-import org.simantics.utils.FileUtils;\r
-\r
-/**\r
- * A facade for Simantics graph database index management facilities.\r
- * \r
- * @author Tuukka Lehtonen\r
- */\r
-public final class DatabaseIndexing {\r
-\r
-    private static final boolean DEBUG = IndexPolicy.TRACE_INDEX_MANAGEMENT;\r
-\r
-    public static File getIndexBaseLocation() {\r
-       return Activator.getDefault().getIndexBaseFile();\r
-//     Activator activator = Activator.getDefault();\r
-//        Bundle b = Platform.getBundle(Activator.BUNDLE_ID);\r
-//        IPath state = Platform.getStateLocation(b);\r
-//        File path = state.append("index").toFile();\r
-//        return path;\r
-    }\r
-\r
-    public static File getIndexLocation(Session session, Resource relation, Resource input) {\r
-        if (session == null)\r
-            throw new NullPointerException("null session");\r
-        if (relation == null)\r
-            throw new NullPointerException("null relation");\r
-        if (input == null)\r
-            throw new NullPointerException("null input");\r
-\r
-        String dir = session.getService(ServerInformation.class).getDatabaseId()\r
-        + "." + relation.getResourceId()\r
-        + "." + input.getResourceId();\r
-\r
-        return new File(getIndexBaseLocation(), dir);\r
-    }\r
-\r
-    private static File getAllDirtyFile() {\r
-        return new File(getIndexBaseLocation(), ".dirty");\r
-    }\r
-\r
-    private static File getChangedFile(File indexPath) {\r
-        return new File(indexPath, ".changed");\r
-    }\r
-\r
-    public static void markAllDirty() throws IOException {\r
-        File indexBase = getIndexBaseLocation();\r
-        if (!indexBase.exists() || !indexBase.isDirectory())\r
-            return;\r
-        if (DEBUG)\r
-            System.out.println("Marking all indexes dirty");\r
-        getAllDirtyFile().createNewFile();\r
-    }\r
-\r
-    public static void clearAllDirty() throws IOException {\r
-        if (DEBUG)\r
-            System.out.println("Clearing dirty state of all indexes");\r
-\r
-        File indexBase = getIndexBaseLocation();\r
-        if (!indexBase.exists() || !indexBase.isDirectory())\r
-            return;\r
-        delete(getAllDirtyFile());\r
-\r
-        forEachIndexPath(new Procedure<File, IOException>() {\r
-            @Override\r
-            public void execute(File indexPath) throws IOException {\r
-                delete(getChangedFile(indexPath));\r
-            }\r
-        });\r
-    }\r
-\r
-    /**\r
-     * Internal to indexing, invoked by {@link IndexedRelationsImpl} which\r
-     * doesn't want to throw these exceptions forward. Just log it.\r
-     * \r
-     * @param indexPath\r
-     */\r
-    static void markIndexChanged(File indexPath) {\r
-        if (!indexPath.exists())\r
-            throw new IllegalArgumentException("index path " + indexPath + " does not exist");\r
-        if (!indexPath.isDirectory())\r
-            throw new IllegalArgumentException("index path " + indexPath + " is not a directory");\r
-        try {\r
-            if (DEBUG)\r
-                System.out.println("Marking index dirty: " + indexPath);\r
-            getChangedFile(indexPath).createNewFile();\r
-        } catch (IOException e) {\r
-            Logger.defaultLogError(e);\r
-        }\r
-    }\r
-\r
-    public static void deleteAllIndexes() throws IOException {\r
-        File indexBase = DatabaseIndexing.getIndexBaseLocation();\r
-        delete(indexBase);\r
-    }\r
-\r
-    public static void deleteIndex(final Resource relation, final Resource modelPart) throws DatabaseException {\r
-\r
-       SimanticsInternal.getSession().syncRequest(new WriteRequest() {\r
-\r
-                       @Override\r
-                       public void perform(WriteGraph graph) throws DatabaseException {\r
-                               deleteIndex(graph, relation, modelPart);\r
-                       }\r
-               \r
-       });\r
-\r
-    }\r
-\r
-    public static void deleteIndex(WriteGraph graph, final Resource relation, final Resource modelPart) throws DatabaseException {\r
-       \r
-       Resource model = graph.syncRequest(new IndexRoot(modelPart));\r
-       GenericRelationIndex index = graph.adapt(relation, GenericRelationIndex.class);\r
-       IndexedRelations ir = graph.getService(IndexedRelations.class);\r
-       // Deletes index files\r
-       ir.reset(null, graph, relation, model);\r
-       // Notifies DB listeners\r
-       index.reset(graph, model);\r
-       \r
-    }\r
-    \r
-    public static void deleteIndex(File indexPath) throws IOException {\r
-        if (DEBUG)\r
-            System.out.println("Deleting index " + indexPath);\r
-        delete(indexPath);\r
-    }\r
-\r
-    public static void validateIndexes() throws IOException {\r
-        File indexBase = getIndexBaseLocation();\r
-        if (DEBUG)\r
-            System.out.println("Validating indexes at " + indexBase);\r
-        if (!indexBase.exists())\r
-            return;\r
-        if (!indexBase.isDirectory()) {\r
-            // Make sure that index-base is a valid directory\r
-            if (DEBUG)\r
-                System.out.println(indexBase + " is not a directory! Removing it.");\r
-            delete(indexBase);\r
-            indexBase.mkdirs();\r
-            return;\r
-        }\r
-        File allDirtyFile = getAllDirtyFile();\r
-        if (allDirtyFile.isFile()) {\r
-            if (DEBUG)\r
-                System.out.println("All indexes marked dirty, removing them.");\r
-            delete(allDirtyFile);\r
-            deleteAllIndexes();\r
-        } else {\r
-            forEachIndexPath(new Procedure<File, IOException>() {\r
-                @Override\r
-                public void execute(File indexPath) throws IOException {\r
-                    File changed = getChangedFile(indexPath);\r
-                    if (changed.isFile()) {\r
-                        if (DEBUG)\r
-                            System.out.println("Index is dirty, removing: " + indexPath);\r
-                        deleteIndex(indexPath);\r
-                    }\r
-                }\r
-            });\r
-        }\r
-    }\r
-\r
-\r
-    private static void delete(File fileOrDir) throws IOException {\r
-        if (fileOrDir.exists())\r
-            FileUtils.deleteAll(fileOrDir);\r
-    }\r
-\r
-    interface Procedure<T, E extends Throwable> {\r
-        void execute(T t) throws E;\r
-    }\r
-\r
-    private static <E extends Throwable> void forEachIndexPath(Procedure<File, E> callback) throws E {\r
-        for (File indexPath : getIndexBaseLocation().listFiles(new FileFilter() {\r
-            @Override\r
-            public boolean accept(File pathname) {\r
-                return pathname.isDirectory();\r
-            }\r
-        })) {\r
-            callback.execute(indexPath);\r
-        }\r
-    }\r
-\r
-}\r
+/*******************************************************************************
+ * Copyright (c) 2007, 2010 Association for Decentralized Information Management
+ * in Industry THTH ry.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     VTT Technical Research Centre of Finland - initial API and implementation
+ *******************************************************************************/
+package org.simantics.db.indexing;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import org.simantics.db.Resource;
+import org.simantics.db.Session;
+import org.simantics.db.WriteGraph;
+import org.simantics.db.common.request.IndexRoot;
+import org.simantics.db.common.request.WriteRequest;
+import org.simantics.db.exception.DatabaseException;
+import org.simantics.db.indexing.internal.IndexChangedWriter;
+import org.simantics.db.layer0.adapter.GenericRelationIndex;
+import org.simantics.db.layer0.genericrelation.IndexedRelations;
+import org.simantics.db.layer0.internal.SimanticsInternal;
+import org.simantics.db.service.ServerInformation;
+import org.simantics.utils.FileUtils;
+import org.slf4j.LoggerFactory;
+
+/**
+ * A facade for Simantics graph database index management facilities.
+ * 
+ * @author Tuukka Lehtonen
+ */
+public final class DatabaseIndexing {
+
+    private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DatabaseIndexing.class);
+
+    public static Path getIndexBaseLocation() {
+        return Activator.getDefault().getIndexBaseFile();
+    }
+
+    public static Path getIndexLocation(Session session, Resource relation, Resource input) {
+        if (session == null)
+            throw new NullPointerException("null session");
+        if (relation == null)
+            throw new NullPointerException("null relation");
+        if (input == null)
+            throw new NullPointerException("null input");
+
+        String dir = session.getService(ServerInformation.class).getDatabaseId()
+        + "." + relation.getResourceId()
+        + "." + input.getResourceId();
+
+        return getIndexBaseLocation().resolve(dir);
+    }
+
+    private static Path getAllDirtyFile() {
+        return getIndexBaseLocation().resolve(".dirty");
+    }
+
+    private static Path getChangedFile(Path indexPath) {
+        return indexPath.resolve(".changed");
+    }
+
+    public static void markAllDirty() throws IOException {
+        Path indexBase = getIndexBaseLocation();
+        if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
+            return;
+        if (LOGGER.isDebugEnabled())
+            LOGGER.debug("Marking all indexes dirty");
+        Path allDirtyFile = getAllDirtyFile();
+        if (!Files.exists(allDirtyFile)) {
+            Files.createFile(allDirtyFile);
+            FileUtils.sync(allDirtyFile);
+        }
+    }
+
+    public static void clearAllDirty() throws IOException {
+        if (LOGGER.isDebugEnabled())
+            LOGGER.debug("Clearing dirty state of all indexes");
+
+        Path indexBase = getIndexBaseLocation();
+        if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
+            return;
+
+        forEachIndexPath(indexPath -> {
+            Path p = getChangedFile(indexPath);
+            try {
+                FileUtils.delete(p);
+            } catch (IOException e) {
+                LOGGER.error("Could not delete {}", p.toAbsolutePath(), e);
+            }
+        });
+
+        FileUtils.delete(getAllDirtyFile());
+    }
+    
+    /**
+     * Internal to indexing, invoked by {@link IndexedRelationsImpl} which
+     * doesn't want to throw these exceptions forward. Just log it.
+     * 
+     * @param indexPath
+     */
+    static void markIndexChanged(Session session, Path indexPath) {
+        if (LOGGER.isDebugEnabled())
+            LOGGER.debug("Marking index dirty: " + indexPath);
+        Path changedFile = getChangedFile(indexPath);
+        try {
+            
+            // Mark change only once per DB session.
+            if (getIndexChangedWriter(session).markDirty(changedFile)) {
+                Files.createDirectories(indexPath);
+                Files.createFile(changedFile);
+                FileUtils.sync(changedFile);
+            }
+        } catch (IOException e) {
+            LOGGER.error("Could not mark index changed for indexPath={} and changedFile={}", indexPath.toAbsolutePath(), changedFile.toAbsolutePath());
+        }
+    }
+
+    private static IndexChangedWriter getIndexChangedWriter(Session session) {
+        IndexChangedWriter writer = session.peekService(IndexChangedWriter.class);
+        if (writer == null) {
+            synchronized (IndexChangedWriter.class) {
+                if (writer == null)
+                    session.registerService(IndexChangedWriter.class, writer = new IndexChangedWriter());
+            }
+        }
+        return writer;
+    }
+
+    public static void deleteAllIndexes() throws IOException {
+        Path indexBase = DatabaseIndexing.getIndexBaseLocation();
+
+        ArrayList<Path> filter = new ArrayList<>(2);
+        filter.add(getAllDirtyFile());
+        filter.add(indexBase);
+
+        FileUtils.deleteWithFilter(indexBase, path -> !filter.contains(path));
+        FileUtils.delete(indexBase);
+    }
+
+    public static void deleteIndex(final Resource relation, final Resource modelPart) throws DatabaseException {
+
+       SimanticsInternal.getSession().syncRequest(new WriteRequest() {
+
+                       @Override
+                       public void perform(WriteGraph graph) throws DatabaseException {
+                               deleteIndex(graph, relation, modelPart);
+                       }
+               
+       });
+
+    }
+
+    public static void deleteIndex(WriteGraph graph, final Resource relation, final Resource modelPart) throws DatabaseException {
+       
+       Resource model = graph.syncRequest(new IndexRoot(modelPart));
+       GenericRelationIndex index = graph.adapt(relation, GenericRelationIndex.class);
+       IndexedRelations ir = graph.getService(IndexedRelations.class);
+       // Deletes index files
+       ir.reset(null, graph, relation, model);
+       // Notifies DB listeners
+       index.reset(graph, model);
+       
+    }
+    
+    public static void deleteIndex(Path indexPath) throws IOException {
+        if (LOGGER.isDebugEnabled())
+            LOGGER.debug("Deleting index " + indexPath.toAbsolutePath());
+
+        ArrayList<Path> filter = new ArrayList<>(2);
+        filter.add(getChangedFile(indexPath));
+        filter.add(indexPath);
+
+        FileUtils.deleteWithFilter(indexPath, path -> !filter.contains(path));
+        FileUtils.delete(indexPath);
+    }
+
+    public static void validateIndexes() throws IOException {
+        Path indexBase = getIndexBaseLocation();
+        if (LOGGER.isDebugEnabled())
+            LOGGER.debug("Validating indexes at " + indexBase);
+        if (!Files.exists(indexBase))
+            return;
+        if (!Files.isDirectory(indexBase)) {
+            // Make sure that index-base is a valid directory
+            if (LOGGER.isDebugEnabled())
+                LOGGER.debug(indexBase + " is not a directory! Removing it.");
+            FileUtils.delete(indexBase);
+            Files.createDirectories(indexBase);
+            return;
+        }
+        Path allDirtyFile = getAllDirtyFile();
+        if (Files.isRegularFile(allDirtyFile)) {
+            if (LOGGER.isDebugEnabled())
+                LOGGER.debug("All indexes marked dirty, removing them.");
+            deleteAllIndexes();
+        } else {
+            forEachIndexPath(indexPath -> {
+                Path changed = getChangedFile(indexPath);
+                if (Files.isRegularFile(changed)) {
+                    if (LOGGER.isDebugEnabled())
+                        LOGGER.debug("Index is dirty, removing: " + indexPath);
+                    try {
+                        deleteIndex(indexPath);
+                    } catch (IOException e) {
+                        LOGGER.error("Could not delete index {}", indexPath.toAbsolutePath(), e);
+                    }
+                }
+            });
+        }
+    }
+
+    private static void forEachIndexPath(Consumer<Path> callback) throws IOException {
+        try (Stream<Path> paths = Files.walk(getIndexBaseLocation(), 1).filter(Files::isDirectory)) {
+            Iterator<Path> iter = paths.iterator();
+            while (iter.hasNext()) {
+                Path p = iter.next();
+                callback.accept(p);
+            }
+        }
+    }
+
+}