1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics.db.indexing;
14 import java.io.IOException;
15 import java.nio.file.FileAlreadyExistsException;
16 import java.nio.file.Files;
17 import java.nio.file.Path;
18 import java.util.ArrayList;
19 import java.util.Iterator;
20 import java.util.function.Consumer;
21 import java.util.stream.Stream;
23 import org.simantics.db.Resource;
24 import org.simantics.db.Session;
25 import org.simantics.db.WriteGraph;
26 import org.simantics.db.common.request.IndexRoot;
27 import org.simantics.db.common.request.WriteRequest;
28 import org.simantics.db.exception.DatabaseException;
29 import org.simantics.db.indexing.internal.IndexChangedWriter;
30 import org.simantics.db.layer0.adapter.GenericRelationIndex;
31 import org.simantics.db.layer0.genericrelation.IndexedRelations;
32 import org.simantics.db.layer0.internal.SimanticsInternal;
33 import org.simantics.db.service.ServerInformation;
34 import org.simantics.utils.FileUtils;
35 import org.slf4j.LoggerFactory;
38 * A facade for Simantics graph database index management facilities.
40 * @author Tuukka Lehtonen
42 public final class DatabaseIndexing {
44 private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DatabaseIndexing.class);
46 public static Path getIndexBaseLocation() {
47 return Activator.getDefault().getIndexBaseFile();
50 public static Path getIndexLocation(Session session, Resource relation, Resource input) {
52 throw new NullPointerException("null session");
54 throw new NullPointerException("null relation");
56 throw new NullPointerException("null input");
58 String dir = session.getService(ServerInformation.class).getDatabaseId()
59 + "." + relation.getResourceId()
60 + "." + input.getResourceId();
62 return getIndexBaseLocation().resolve(dir);
65 private static Path getAllDirtyFile() {
66 return getIndexBaseLocation().resolve(".dirty");
69 private static Path getChangedFile(Path indexPath) {
70 return indexPath.resolve(".changed");
73 public static void markAllDirty() throws IOException {
74 Path indexBase = getIndexBaseLocation();
75 if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
77 if (LOGGER.isDebugEnabled())
78 LOGGER.debug("Marking all indexes dirty");
79 Path allDirtyFile = getAllDirtyFile();
80 if (!Files.exists(allDirtyFile)) {
81 Files.createFile(allDirtyFile);
82 FileUtils.sync(allDirtyFile);
86 public static void clearAllDirty() throws IOException {
87 if (LOGGER.isDebugEnabled())
88 LOGGER.debug("Clearing dirty state of all indexes");
90 Path indexBase = getIndexBaseLocation();
91 if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
94 forEachIndexPath(indexPath -> {
95 Path p = getChangedFile(indexPath);
98 } catch (IOException e) {
99 LOGGER.error("Could not delete {}", p.toAbsolutePath(), e);
103 FileUtils.delete(getAllDirtyFile());
107 * Internal to indexing, invoked by {@link IndexedRelationsImpl} which
108 * doesn't want to throw these exceptions forward. Just log it.
112 static void markIndexChanged(Session session, Path indexPath) {
113 if (LOGGER.isDebugEnabled())
114 LOGGER.debug("Marking index dirty: " + indexPath);
115 Path changedFile = getChangedFile(indexPath);
118 // Mark change only once per DB session.
119 if (getIndexChangedWriter(session).markDirty(changedFile)) {
120 Files.createDirectories(indexPath);
121 if (!Files.exists(changedFile)) {
122 Files.createFile(changedFile);
123 FileUtils.sync(changedFile);
124 } else if (!Files.isRegularFile(changedFile)) {
125 throw new FileAlreadyExistsException(changedFile.toString(), null, "index dirtyness indicator file already exists but it is not a regular file");
128 } catch (IOException e) {
129 LOGGER.error("Could not mark index changed for indexPath={} and changedFile={}", indexPath.toAbsolutePath(), changedFile.toAbsolutePath());
133 private static IndexChangedWriter getIndexChangedWriter(Session session) {
134 IndexChangedWriter writer = session.peekService(IndexChangedWriter.class);
135 if (writer == null) {
136 synchronized (IndexChangedWriter.class) {
138 session.registerService(IndexChangedWriter.class, writer = new IndexChangedWriter());
144 public static void deleteAllIndexes() throws IOException {
145 Path indexBase = DatabaseIndexing.getIndexBaseLocation();
147 ArrayList<Path> filter = new ArrayList<>(2);
148 filter.add(getAllDirtyFile());
149 filter.add(indexBase);
151 FileUtils.deleteWithFilter(indexBase, path -> !filter.contains(path));
152 FileUtils.delete(indexBase);
155 public static void deleteIndex(final Resource relation, final Resource modelPart) throws DatabaseException {
157 SimanticsInternal.getSession().syncRequest(new WriteRequest() {
160 public void perform(WriteGraph graph) throws DatabaseException {
161 deleteIndex(graph, relation, modelPart);
168 public static void deleteIndex(WriteGraph graph, final Resource relation, final Resource modelPart) throws DatabaseException {
170 Resource model = graph.syncRequest(new IndexRoot(modelPart));
171 GenericRelationIndex index = graph.adapt(relation, GenericRelationIndex.class);
172 IndexedRelations ir = graph.getService(IndexedRelations.class);
173 // Deletes index files
174 ir.reset(null, graph, relation, model);
175 // Notifies DB listeners
176 index.reset(graph, model);
180 public static void deleteIndex(Path indexPath) throws IOException {
181 if (LOGGER.isDebugEnabled())
182 LOGGER.debug("Deleting index " + indexPath.toAbsolutePath());
184 ArrayList<Path> filter = new ArrayList<>(2);
185 filter.add(getChangedFile(indexPath));
186 filter.add(indexPath);
188 FileUtils.deleteWithFilter(indexPath, path -> !filter.contains(path));
189 FileUtils.delete(indexPath);
192 public static void validateIndexes() throws IOException {
193 Path indexBase = getIndexBaseLocation();
194 if (LOGGER.isDebugEnabled())
195 LOGGER.debug("Validating indexes at " + indexBase);
196 if (!Files.exists(indexBase))
198 if (!Files.isDirectory(indexBase)) {
199 // Make sure that index-base is a valid directory
200 if (LOGGER.isDebugEnabled())
201 LOGGER.debug(indexBase + " is not a directory! Removing it.");
202 FileUtils.emptyDirectory(indexBase);
203 Files.createDirectories(indexBase);
206 Path allDirtyFile = getAllDirtyFile();
207 if (Files.isRegularFile(allDirtyFile)) {
208 if (LOGGER.isDebugEnabled())
209 LOGGER.debug("All indexes marked dirty, removing them.");
212 forEachIndexPath(indexPath -> {
213 Path changed = getChangedFile(indexPath);
214 if (Files.isRegularFile(changed)) {
215 if (LOGGER.isDebugEnabled())
216 LOGGER.debug("Index is dirty, removing: " + indexPath);
218 deleteIndex(indexPath);
219 } catch (IOException e) {
220 LOGGER.error("Could not delete index {}", indexPath.toAbsolutePath(), e);
227 private static void forEachIndexPath(Consumer<Path> callback) throws IOException {
228 try (Stream<Path> paths = Files.walk(getIndexBaseLocation(), 1).filter(Files::isDirectory)) {
229 Iterator<Path> iter = paths.iterator();
230 while (iter.hasNext()) {
231 Path p = iter.next();