-/*******************************************************************************\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.FileAlreadyExistsException;
+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);
+ if (!Files.exists(changedFile)) {
+ Files.createFile(changedFile);
+ FileUtils.sync(changedFile);
+ } else if (!Files.isRegularFile(changedFile)) {
+ throw new FileAlreadyExistsException(changedFile.toString(), null, "index dirtyness indicator file already exists but it is not a regular file");
+ }
+ }
+ } 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.emptyDirectory(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);
+ }
+ }
+ }
+
+}