Worked around Windows FS problems in IndexedRelationsSearcherBase
[simantics/platform.git] / bundles / org.simantics.db.indexing / src / org / simantics / db / indexing / DatabaseIndexing.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.db.indexing;
13
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;
22
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;
36
37 /**
38  * A facade for Simantics graph database index management facilities.
39  * 
40  * @author Tuukka Lehtonen
41  */
42 public final class DatabaseIndexing {
43
44     private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(DatabaseIndexing.class);
45
46     public static Path getIndexBaseLocation() {
47         return Activator.getDefault().getIndexBaseFile();
48     }
49
50     public static Path getIndexLocation(Session session, Resource relation, Resource input) {
51         if (session == null)
52             throw new NullPointerException("null session");
53         if (relation == null)
54             throw new NullPointerException("null relation");
55         if (input == null)
56             throw new NullPointerException("null input");
57
58         String dir = session.getService(ServerInformation.class).getDatabaseId()
59         + "." + relation.getResourceId()
60         + "." + input.getResourceId();
61
62         return getIndexBaseLocation().resolve(dir);
63     }
64
65     private static Path getAllDirtyFile() {
66         return getIndexBaseLocation().resolve(".dirty");
67     }
68
69     private static Path getChangedFile(Path indexPath) {
70         return indexPath.resolve(".changed");
71     }
72
73     public static void markAllDirty() throws IOException {
74         Path indexBase = getIndexBaseLocation();
75         if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
76             return;
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);
83         }
84     }
85
86     public static void clearAllDirty() throws IOException {
87         if (LOGGER.isDebugEnabled())
88             LOGGER.debug("Clearing dirty state of all indexes");
89
90         Path indexBase = getIndexBaseLocation();
91         if (!Files.exists(indexBase) || !Files.isDirectory(indexBase))
92             return;
93
94         forEachIndexPath(indexPath -> {
95             Path p = getChangedFile(indexPath);
96             try {
97                 FileUtils.delete(p);
98             } catch (IOException e) {
99                 LOGGER.error("Could not delete {}", p.toAbsolutePath(), e);
100             }
101         });
102
103         FileUtils.delete(getAllDirtyFile());
104     }
105     
106     /**
107      * Internal to indexing, invoked by {@link IndexedRelationsImpl} which
108      * doesn't want to throw these exceptions forward. Just log it.
109      * 
110      * @param indexPath
111      */
112     static void markIndexChanged(Session session, Path indexPath) {
113         if (LOGGER.isDebugEnabled())
114             LOGGER.debug("Marking index dirty: " + indexPath);
115         Path changedFile = getChangedFile(indexPath);
116         try {
117             
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");
126                 }
127             }
128         } catch (IOException e) {
129             LOGGER.error("Could not mark index changed for indexPath={} and changedFile={}", indexPath.toAbsolutePath(), changedFile.toAbsolutePath());
130         }
131     }
132
133     private static IndexChangedWriter getIndexChangedWriter(Session session) {
134         IndexChangedWriter writer = session.peekService(IndexChangedWriter.class);
135         if (writer == null) {
136             synchronized (IndexChangedWriter.class) {
137                 if (writer == null)
138                     session.registerService(IndexChangedWriter.class, writer = new IndexChangedWriter());
139             }
140         }
141         return writer;
142     }
143
144     public static void deleteAllIndexes() throws IOException {
145         Path indexBase = DatabaseIndexing.getIndexBaseLocation();
146
147         ArrayList<Path> filter = new ArrayList<>(2);
148         filter.add(getAllDirtyFile());
149         filter.add(indexBase);
150
151         FileUtils.deleteWithFilter(indexBase, path -> !filter.contains(path));
152         FileUtils.delete(indexBase);
153     }
154
155     public static void deleteIndex(final Resource relation, final Resource modelPart) throws DatabaseException {
156
157         SimanticsInternal.getSession().syncRequest(new WriteRequest() {
158
159                         @Override
160                         public void perform(WriteGraph graph) throws DatabaseException {
161                                 deleteIndex(graph, relation, modelPart);
162                         }
163                 
164         });
165
166     }
167
168     public static void deleteIndex(WriteGraph graph, final Resource relation, final Resource modelPart) throws DatabaseException {
169         
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);
177         
178     }
179     
180     public static void deleteIndex(Path indexPath) throws IOException {
181         if (LOGGER.isDebugEnabled())
182             LOGGER.debug("Deleting index " + indexPath.toAbsolutePath());
183
184         ArrayList<Path> filter = new ArrayList<>(2);
185         filter.add(getChangedFile(indexPath));
186         filter.add(indexPath);
187
188         FileUtils.deleteWithFilter(indexPath, path -> !filter.contains(path));
189         FileUtils.delete(indexPath);
190     }
191
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))
197             return;
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);
204             return;
205         }
206         Path allDirtyFile = getAllDirtyFile();
207         if (Files.isRegularFile(allDirtyFile)) {
208             if (LOGGER.isDebugEnabled())
209                 LOGGER.debug("All indexes marked dirty, removing them.");
210             deleteAllIndexes();
211         } else {
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);
217                     try {
218                         deleteIndex(indexPath);
219                     } catch (IOException e) {
220                         LOGGER.error("Could not delete index {}", indexPath.toAbsolutePath(), e);
221                     }
222                 }
223             });
224         }
225     }
226
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();
232                 callback.accept(p);
233             }
234         }
235     }
236
237 }