]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.db.server/src/org/simantics/db/server/internal/DatabaseI.java
Migrated source code from Simantics SVN
[simantics/platform.git] / bundles / org.simantics.db.server / src / org / simantics / db / server / internal / DatabaseI.java
1 package org.simantics.db.server.internal;\r
2 \r
3 import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;\r
4 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;\r
5 \r
6 import java.io.EOFException;\r
7 import java.io.File;\r
8 import java.io.FilenameFilter;\r
9 import java.io.IOException;\r
10 import java.io.OutputStream;\r
11 import java.io.RandomAccessFile;\r
12 import java.nio.file.DirectoryStream;\r
13 import java.nio.file.FileSystems;\r
14 import java.nio.file.FileVisitOption;\r
15 import java.nio.file.FileVisitResult;\r
16 import java.nio.file.Files;\r
17 import java.nio.file.Path;\r
18 import java.nio.file.PathMatcher;\r
19 import java.nio.file.Paths;\r
20 import java.nio.file.SimpleFileVisitor;\r
21 import java.nio.file.attribute.BasicFileAttributes;\r
22 import java.util.ArrayList;\r
23 import java.util.EnumSet;\r
24 import java.util.HashMap;\r
25 import java.util.List;\r
26 import java.util.Properties;\r
27 import java.util.TreeMap;\r
28 import java.util.UUID;\r
29 \r
30 import org.simantics.databoard.Bindings;\r
31 import org.simantics.databoard.binding.impl.TreeMapBinding;\r
32 import org.simantics.databoard.serialization.Serializer;\r
33 import org.simantics.db.Database;\r
34 import org.simantics.db.DatabaseUserAgent;\r
35 import org.simantics.db.ServiceLocator;\r
36 import org.simantics.db.common.CommentMetadata;\r
37 import org.simantics.db.common.CommitMetadata;\r
38 import org.simantics.db.common.utils.Logger;\r
39 import org.simantics.db.common.utils.MetadataUtil;\r
40 import org.simantics.db.exception.SDBException;\r
41 import org.simantics.db.server.DatabaseLastExitException;\r
42 import org.simantics.db.server.ProCoreException;\r
43 import org.simantics.db.server.internal.ProCoreServer.TailFile;\r
44 import org.simantics.db.server.protocol.MessageNumber;\r
45 import org.simantics.db.server.protocol.MessageText;\r
46 \r
47 public class DatabaseI implements Database {\r
48     private static String NL = System.getProperty("line.separator");\r
49     private static String TEMP_PREFIX = "db.temp.";\r
50     public static DatabaseI newDatabaseI(File dbFolder) {\r
51         return new DatabaseI(dbFolder);\r
52     }\r
53     private final SessionManager sessionManager = new SessionManager();\r
54     private final Server server;\r
55     private final JournalI journal;\r
56     private DatabaseUserAgent dbUserAgent = null;\r
57     private DatabaseI(File dbFolder) {\r
58         server = new Server(dbFolder);\r
59         journal = new JournalI(dbFolder);\r
60     }\r
61     public boolean isSubFolder(Path base, Path sub)\r
62     throws IOException {\r
63         if (null == base || null == sub)\r
64             return false;\r
65         return isSubFolder(base.toFile(), sub.toFile());\r
66     }\r
67     public boolean isSubFolder(File base, File sub)\r
68     throws IOException {\r
69         if (null == base || null == sub)\r
70             return false;\r
71         Path basePath = base.getCanonicalFile().toPath();\r
72         Path subPath = sub.getCanonicalFile().toPath();\r
73         if (subPath.startsWith(basePath))\r
74             return true;\r
75         else\r
76             return false;\r
77     }\r
78     public Path saveDatabase()\r
79     throws ProCoreException {\r
80         Path dbFolder = getFolder().toPath();\r
81         Path parent = getFolder().getParentFile().toPath();\r
82         Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
83         copyTree(dbFolder, temp, FileOption.IF_NEW);\r
84         return temp;\r
85     }\r
86     public Path saveDatabase(Path parent)\r
87     throws ProCoreException {\r
88         Path dbFolder = getFolder().toPath();\r
89         try {\r
90             boolean yes = isSubFolder(dbFolder, parent);\r
91             if (yes)\r
92                 throw new ProCoreException("Parent must not be subdirectory of database directory."\r
93                         + NL + "database=" + dbFolder\r
94                         + NL + "parent=" + parent);\r
95         } catch (IOException e) {\r
96             throw new ProCoreException("Failed to save database to parent."\r
97                     + NL + "database=" + dbFolder\r
98                     + NL + "parent=" + parent);\r
99         }\r
100         Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
101         copyTree(dbFolder, temp, FileOption.IF_NEW);\r
102         return temp;\r
103     }\r
104     public void deleteDatabaseFiles() throws ProCoreException {\r
105         if (server.isAlive()) {\r
106             server.tryToStop();\r
107             if (server.isAlive())\r
108                 throw new ProCoreException("Server must be dead to delete database.");\r
109         }\r
110         Path db = getFolder().toPath();\r
111         deleteDatabaseFiles(db); // Redundant, but tests the method.\r
112         deleteTree(db);\r
113     }\r
114     Path copyDatabaseFiles(Path from, Path to) throws ProCoreException {\r
115         copyTree(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_EXIST);\r
116         if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
117             copyTree(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_EXIST);\r
118         copyPath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
119         copyPath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
120         copyPath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
121         copyPath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
122         copyFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_EXIST);\r
123         return to;\r
124     }\r
125     public static void deleteDatabaseFiles(Path from) throws ProCoreException {\r
126         deleteFile(from, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
127         deleteFile(from, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
128         deleteFile(from, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
129         deleteFile(from, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
130         deleteFile(from, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
131         deleteFile(from, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
132         deleteFile(from, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
133         deleteFiles(from, ProCoreServer.PAGE_FILE_PATTERN);\r
134         deleteTree(from.resolve(ProCoreServer.BRANCH_DIR));\r
135         deleteFile(from, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
136         deleteTree(from.resolve(ProCoreServer.TAIL_DIR));\r
137     }\r
138     Path moveDatabaseFiles(Path from, Path to) throws ProCoreException {\r
139         moveFolder(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_NEW);\r
140         if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
141             moveFolder(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_NEW);\r
142         movePath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
143         movePath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_NEW);\r
144         movePath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
145         movePath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_NEW);\r
146         moveFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_NEW);\r
147         movePath(from, to, ProCoreServer.DCS_FILE, FileOption.IF_NEW);\r
148         moveFile(from, to, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_NEW);\r
149         moveFile(from, to, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_NEW);\r
150         moveFile(from, to, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_NEW);\r
151         return to;\r
152     }\r
153     private void moveFile(Path from, Path to, String file, FileOption fileOption) throws ProCoreException {\r
154         if (Files.exists(from.resolve(file)))\r
155             movePath(from, to, file, fileOption);\r
156     }\r
157     public void serverCreateConfiguration()  throws ProCoreException {\r
158         server.createConfiguration();\r
159     }\r
160     public boolean ignoreExit() throws ProCoreException {\r
161         if (!isFolderOk())\r
162             throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
163         server.deleteGuard();\r
164         File folder = server.getFolder();\r
165         Path db = Paths.get(folder.getAbsolutePath());\r
166         Path ri = db.resolve(ProCoreServer.RECOVERY_IGNORED_FILE);\r
167         if (!Files.exists(ri))\r
168             try (OutputStream os = Files.newOutputStream(ri)) {\r
169             } catch (IOException e) {\r
170                 throw new ProCoreException("Could not create file: " + ri, e);\r
171             }\r
172         boolean ok = false;\r
173         DatabaseUserAgent dbu = dbUserAgent; // Save current user agent.\r
174         try {\r
175             dbUserAgent = null; // Recursion not supported. Disable user agent.\r
176             server.start();\r
177             ok = server.isActive();\r
178         } finally {\r
179             try {\r
180                 dbUserAgent = dbu; // Restore used user agent.\r
181                 server.stop();\r
182             } finally {\r
183                 try {\r
184                     Files.deleteIfExists(ri);\r
185                 } catch (IOException e) {\r
186                     Logger.defaultLogError("Could not delete file: " + ri, e);\r
187                 }\r
188                 Path rn = db.resolve(ProCoreServer.RECOVERY_NEEDED_FILE);\r
189                 try {\r
190                     Files.deleteIfExists(rn);\r
191                 } catch (IOException e) {\r
192                     Logger.defaultLogError("Could not delete file: " + rn, e);\r
193                 }\r
194             }\r
195         }\r
196         return ok;\r
197     }\r
198     public boolean ignoreProtocol() throws ProCoreException {\r
199         if (!isFolderOk())\r
200             throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
201         File folder = server.getFolder();\r
202         Path db = Paths.get(folder.getAbsolutePath());\r
203         Path ri = db.resolve(ProCoreServer.PROTOCOL_IGNORED_FILE);\r
204         if (!Files.exists(ri))\r
205             try (OutputStream os = Files.newOutputStream(ri)) {\r
206             } catch (IOException e) {\r
207                 throw new ProCoreException("Could not create file: " + ri, e);\r
208             }\r
209         boolean ok = false;\r
210         try {\r
211             server.start();\r
212             ok = server.isActive();\r
213         } finally {\r
214             server.stop();\r
215         }\r
216         return ok;\r
217     }\r
218     private long preJournalCheck() throws ProCoreException {\r
219         File folder = server.getFolder();\r
220         if (!folder.isDirectory())\r
221             throw new ProCoreException("Database folder does not exist." + NL + "folder=" + folder);\r
222         File file = new File(folder, ProCoreServer.JOURNAL_FILE);\r
223         if (!file.isFile())\r
224             throw new ProCoreException("Journal file does not exist." + NL + "file=" + file);\r
225         else if (!file.canRead())\r
226             throw new ProCoreException("Journal file must be readale to create database from journal." + NL + "file=" + file);\r
227         else if (server.isAlive())\r
228             throw new ProCoreException("Server must be dead to create database from journal file." + NL + "file=" + file);\r
229         return getNextClusterId(folder.toPath());\r
230     }\r
231     private void postJournalFix(long nextFreeId)  throws SDBException {\r
232         Session s = newSession(null);\r
233         long current =  s.reserveIds(0);\r
234         if (current < nextFreeId)\r
235             s.reserveIds((int)(nextFreeId - current));\r
236         s.isClosed();\r
237     }\r
238     private Path createTempFolder(Path parent, String fail) throws ProCoreException {\r
239         try {\r
240             return Files.createTempDirectory(parent, TEMP_PREFIX);\r
241         } catch (IOException e) {\r
242             throw new ProCoreException(fail, e);\r
243         }\r
244     }\r
245     public void replaceFromJournal() throws SDBException {\r
246         long nextFreeId = preJournalCheck();\r
247         Path db = Paths.get(server.getFolder().getAbsolutePath());\r
248         Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from journal.");\r
249         movePath(db, temp, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
250         movePath(db, temp, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
251         deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
252         deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
253         Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
254         deleteFiles(dbHead, ProCoreServer.VALUE_PATTERN);\r
255         deleteFiles(dbHead, ProCoreServer.DATA_PATTERN);\r
256         deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
257         Path dbHeadTailFile = dbHead.resolve(ProCoreServer.TAIL_FILE);\r
258         boolean headTailFileExisted = Files.exists(dbHeadTailFile);\r
259         final long NEXT_REVISION = 1;\r
260         if (!headTailFileExisted) // Number of change sets and database id not known at this point. Fortunately they are not used by recovery.\r
261             TailFile.createTailFile(dbHeadTailFile.toFile(), NEXT_REVISION, nextFreeId, UUID.randomUUID().toString());\r
262         movePath(dbHead, temp, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
263         Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
264         boolean tailExisted = Files.isDirectory(dbTail);\r
265         if (tailExisted)\r
266             copyPath(dbTail, dbHead, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
267         else {\r
268             try {\r
269                 Files.createDirectory(dbTail);\r
270             } catch (IOException e) {\r
271                 throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
272             }\r
273             copyPath(temp, dbTail, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
274         }\r
275         server.createConfiguration();\r
276         server.start();\r
277         try {\r
278             String t = server.execute("journalRead " + temp.getFileName());\r
279             if (t.length() > 0)\r
280                 throw new ProCoreException("Could not read journal. reply=" + t);\r
281             postJournalFix(nextFreeId); // Not the right way for implementing this, but better than incorrect recovery.\r
282         } finally {\r
283             server.tryToStop();\r
284         }\r
285         movePath(temp, db, ProCoreServer.CONFIG_FILE, FileOption.OVERWRITE);\r
286         deleteTree(temp);\r
287         if (!tailExisted)\r
288             deleteTree(dbTail);\r
289     }\r
290     public boolean canPurge() throws ProCoreException {\r
291         File folder = server.getFolder();\r
292         Path db = Paths.get(folder.getAbsolutePath());\r
293         Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
294         if (!Files.exists(dbHead))\r
295             return false; // Already clean.\r
296         boolean empty = isFolderEmpty(dbHead);\r
297         if (empty)\r
298             return false; // Already clean.\r
299         final boolean[] found = new boolean[1];\r
300         found[0] = false;\r
301         dbHead.toFile().listFiles(new FilenameFilter() {\r
302             @Override\r
303             public boolean accept(File dir, String name) {\r
304                 if (found[0])\r
305                     return false;\r
306                 else if (!name.equals(ProCoreServer.TAIL_FILE)) {\r
307                     found[0] = true;\r
308                     return false;\r
309                 }\r
310                 return true;\r
311             }\r
312         });\r
313         return found[0];\r
314     }\r
315     /**\r
316      * Removes old history i.e. change sets.\r
317      * Note that also cleans last exit status and removes guard file.\r
318      * This means that all information about last exit status is also lost\r
319      * .\r
320      * @throws ProCoreException\r
321      */\r
322     public void purge() throws SDBException {\r
323         synchronized (server.proCoreServer.getProCoreClient()) {\r
324         boolean wasAlive = server.isAlive();\r
325         if (wasAlive)\r
326             server.stop();\r
327         Path db = Paths.get(server.getFolder().getAbsolutePath());\r
328         Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
329         Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
330         if (!Files.isDirectory(dbTail)) {\r
331             try {\r
332                 Files.createDirectory(dbTail);\r
333             } catch (IOException e) {\r
334                 throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
335             }\r
336         }\r
337         long nextFreeId = getNextClusterId(db);\r
338         boolean cleanHead = Files.isDirectory(dbHead) && !isFolderEmpty(dbHead);\r
339         Logger.defaultLog("Purging old history and exit information. folder=" + db);\r
340         if (cleanHead) {\r
341             deleteClusters(dbHead, dbTail);\r
342             movePath(dbHead, dbTail, ProCoreServer.TAIL_FILE, FileOption.OVERWRITE);\r
343             moveFiles(dbHead, dbTail, ProCoreServer.VALUE_PATTERN, FileOption.IF_NEW);\r
344             moveFiles(dbHead, dbTail, ProCoreServer.DATA_PATTERN, FileOption.OVERWRITE);\r
345             deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
346         }\r
347         deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
348         deleteFile(db, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
349         deleteFile(db, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
350         deleteFile(db, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
351         deleteFile(db, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
352         deleteFile(db, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
353         deleteFile(db, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
354         deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
355         purgeValues(dbTail);\r
356         server.start();\r
357         Session s = newSession(null);\r
358         long current =  s.reserveIds(0);\r
359         if (current < nextFreeId)\r
360             s.reserveIds((int)(nextFreeId - current));\r
361         s.isClosed();\r
362         if (!wasAlive)\r
363             server.tryToStop();\r
364         }\r
365     }\r
366     private void copyPath(Path fromFolder, Path toFolder, String file, FileOption fileOption) throws ProCoreException {\r
367         Path from = fromFolder.resolve(file);\r
368         Path to = toFolder.resolve(file);\r
369         try {\r
370             if (FileOption.IF_EXIST.equals(fileOption)) {\r
371                 if (!Files.exists(from))\r
372                     return;\r
373             }\r
374             if (FileOption.OVERWRITE.equals(fileOption))\r
375                 Files.copy(from, to, COPY_ATTRIBUTES, REPLACE_EXISTING);\r
376             else\r
377                 Files.copy(from, to, COPY_ATTRIBUTES);\r
378         } catch (IOException e) {\r
379             throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
380         }\r
381     }\r
382     private static void deleteFile(Path from, String file, FileOption fileOption) throws ProCoreException {\r
383         Path path = from.resolve(file);\r
384         deletePath(path, fileOption);\r
385     }\r
386     private static void deletePath(Path path, FileOption fileOption) throws ProCoreException {\r
387         try {\r
388             if (FileOption.IF_EXIST.equals(fileOption))\r
389                 Files.deleteIfExists(path);\r
390             else\r
391                 Files.delete(path);\r
392         } catch (IOException e) {\r
393             throw new ProCoreException("Could not delete " + path, e);\r
394         }\r
395     }\r
396     private boolean isFolderEmpty(final Path folder) { // True if folder exists and is empty.\r
397         if (!Files.isDirectory(folder))\r
398             return false;\r
399         try(DirectoryStream<Path> folderStream = Files.newDirectoryStream(folder)) {\r
400             return !folderStream.iterator().hasNext();\r
401         } catch (IOException e) {\r
402             Logger.defaultLogError("Failed to open folder stream. folder=" + folder, e);\r
403             return false;\r
404         }\r
405     }\r
406     private void movePath(Path fromPath, Path toPath, String file, FileOption fileOption) throws ProCoreException {\r
407         Path from = fromPath.resolve(file);\r
408         Path to = toPath.resolve(file);\r
409         try {\r
410             if (FileOption.OVERWRITE.equals(fileOption))\r
411                 Files.move(from, to, REPLACE_EXISTING);\r
412             else\r
413                 Files.move(from, to);\r
414         } catch (IOException e) {\r
415             throw new ProCoreException("Could not move " + from + " to " + to, e);\r
416         }\r
417     }\r
418     private static void copyTree(Path from, Path to, String path, final FileOption fileOption) throws ProCoreException {\r
419         copyTree(from.resolve(path), to.resolve(path), fileOption);\r
420     }\r
421     private static void copyTree(Path from, Path to, final FileOption fileOption) throws ProCoreException {\r
422         class Visitor extends SimpleFileVisitor<Path> {\r
423             private Path fromPath;\r
424             private Path toPath;\r
425             Visitor(Path from, Path to) {\r
426                 fromPath = from;\r
427                 toPath = to;\r
428             }\r
429             @Override\r
430             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
431                 Path targetPath = toPath.resolve(fromPath.relativize(dir));\r
432                 if (!Files.exists(targetPath)) {\r
433                     Files.createDirectory(targetPath);\r
434                 }\r
435                 return FileVisitResult.CONTINUE;\r
436             }\r
437             @Override\r
438             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
439                 if (FileOption.OVERWRITE.equals(fileOption))\r
440                     Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES, REPLACE_EXISTING);\r
441                 else\r
442                     Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES);\r
443                 return FileVisitResult.CONTINUE;\r
444             }\r
445         }\r
446         try {\r
447             Visitor v = new Visitor(from, to);\r
448             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
449             Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
450         } catch (IOException e) {\r
451             throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
452         }\r
453     }\r
454     private static void deleteTree(Path path) throws ProCoreException {\r
455         if (!Files.exists(path))\r
456             return;\r
457         class Visitor extends SimpleFileVisitor<Path> {\r
458             @Override\r
459             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
460                 try {\r
461                     Files.delete(file);\r
462                 } catch (IOException ioe) {\r
463                     ioe.printStackTrace();\r
464                     throw ioe;\r
465                 }\r
466                 return FileVisitResult.CONTINUE;\r
467             }\r
468             @Override\r
469             public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {\r
470                 if (e == null) {\r
471                     try {\r
472                         Files.delete(dir);\r
473                     } catch (IOException ioe) {\r
474                         ioe.printStackTrace();\r
475                         throw ioe;\r
476                     }\r
477                     return FileVisitResult.CONTINUE;\r
478                 }\r
479                 throw e;\r
480             }\r
481         }\r
482         try {\r
483             Visitor v = new Visitor();\r
484             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
485             Files.walkFileTree(path, opts, Integer.MAX_VALUE, v);\r
486         } catch (IOException e) {\r
487             throw new ProCoreException("Could not delete " + path, e);\r
488         }\r
489     }\r
490     private static void moveFolder(Path fromPath, Path toPath, String folder, FileOption fileOption) throws ProCoreException {\r
491         Path from = fromPath.resolve(folder);\r
492         Path to = toPath.resolve(folder);\r
493         copyTree(from, to, fileOption);\r
494         deleteTree(from);\r
495     }\r
496     enum FileOption {\r
497         IF_EXIST,\r
498         IF_NEW,\r
499         OVERWRITE\r
500     }\r
501     private void copyFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
502         class Visitor extends SimpleFileVisitor<Path> {\r
503             private final PathMatcher matcher;\r
504             Visitor(String pattern) {\r
505                 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
506             }\r
507             @Override\r
508             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
509                 Path name = file.getFileName();\r
510                 if (null == name)\r
511                     return FileVisitResult.CONTINUE;\r
512                 else if (!matcher.matches(name))\r
513                     return FileVisitResult.CONTINUE;\r
514                 if (FileOption.OVERWRITE.equals(fileOption))\r
515                     Files.copy(file, to.resolve(name), REPLACE_EXISTING);\r
516                 else\r
517                     Files.copy(file, to.resolve(name));\r
518                 return FileVisitResult.CONTINUE;\r
519             }\r
520             @Override\r
521             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
522                 if (dir.equals(from))\r
523                     return FileVisitResult.CONTINUE;\r
524                 else\r
525                     return FileVisitResult.SKIP_SUBTREE;\r
526             }\r
527         }\r
528         try {\r
529             Visitor v = new Visitor(pattern);\r
530             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
531             Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
532         } catch (IOException e) {\r
533             throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
534         }\r
535     }\r
536     private void moveFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
537         class Visitor extends SimpleFileVisitor<Path> {\r
538             private final PathMatcher matcher;\r
539             Visitor(String pattern) {\r
540                 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
541             }\r
542             @Override\r
543             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
544                 Path name = file.getFileName();\r
545                 if (null == name)\r
546                     return FileVisitResult.CONTINUE;\r
547                 else if (!matcher.matches(name))\r
548                     return FileVisitResult.CONTINUE;\r
549                 if (FileOption.OVERWRITE.equals(fileOption))\r
550                     Files.move(file, to.resolve(name), REPLACE_EXISTING);\r
551                 else\r
552                     Files.move(file, to.resolve(name));\r
553                 return FileVisitResult.CONTINUE;\r
554             }\r
555             @Override\r
556             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
557                 if (dir.equals(from))\r
558                     return FileVisitResult.CONTINUE;\r
559                 else\r
560                     return FileVisitResult.SKIP_SUBTREE;\r
561             }\r
562         }\r
563         try {\r
564             Visitor v = new Visitor(pattern);\r
565             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
566             Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
567         } catch (IOException e) {\r
568             throw new ProCoreException("Could not move " + from + " to " + to, e);\r
569         }\r
570     }\r
571     private void deleteClusters(final Path head, final Path tail) throws ProCoreException {\r
572         class Visitor extends SimpleFileVisitor<Path> {\r
573             private final PathMatcher matcher;\r
574             Visitor(String pattern) {\r
575                 matcher = FileSystems.getDefault().getPathMatcher("glob:" + ProCoreServer.DELETED_PATTERN);\r
576             }\r
577             @Override\r
578             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
579                 Path name = file.getFileName();\r
580                 if (null == name)\r
581                     return FileVisitResult.CONTINUE;\r
582                 else if (!matcher.matches(name))\r
583                     return FileVisitResult.CONTINUE;\r
584                 String deletedStr = name.toString();\r
585                 String indexName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.INDEX_PREFIX);\r
586                 String dataName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.DATA_PREFIX);\r
587                 String[] ss = deletedStr.split("\\.");\r
588                 String valuePattern = ProCoreServer.VALUE_PREFIX + ss[1] + "." + ss[2] + "*";\r
589                 Files.delete(file);\r
590                 Files.delete(head.resolve(indexName));\r
591                 Files.delete(head.resolve(dataName));\r
592                 try {\r
593                     deleteFiles(head, valuePattern);\r
594                     if (Files.isDirectory(tail)) {\r
595                         Files.deleteIfExists(tail.resolve(dataName));\r
596                         deleteFiles(tail, valuePattern);\r
597                     }\r
598                 } catch (ProCoreException e) {\r
599                     throw new IOException("Delete values failed.", e);\r
600                 }\r
601                 return FileVisitResult.CONTINUE;\r
602             }\r
603             @Override\r
604             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
605                 if (dir.equals(head))\r
606                     return FileVisitResult.CONTINUE;\r
607                 else\r
608                     return FileVisitResult.SKIP_SUBTREE;\r
609             }\r
610         }\r
611         try {\r
612             Visitor v = new Visitor(ProCoreServer.DELETED_PREFIX);\r
613             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
614             Files.walkFileTree(head, opts, Integer.MAX_VALUE, v);\r
615         } catch (IOException e) {\r
616             throw new ProCoreException("Could not delete cluster(s).\nhead=" + head + "\ntail=" + tail);\r
617         }\r
618     }\r
619     private static long convertHexStringToLong(String hex) {\r
620         if (hex.length() < 16)\r
621             return Long.parseLong(hex, 16);\r
622         long f = Long.parseLong(hex.substring(0,1), 16) << 60;\r
623         long l = Long.parseLong(hex.substring(1,16), 16);\r
624         return f | l;\r
625     }\r
626     private void purgeValues(final Path folder) throws ProCoreException {\r
627         final class Value {\r
628             long first; // First part of cluster id.\r
629             long second; // Secon part of cluster id.\r
630             int index; // Resource index of the value.\r
631             transient long cs; // Change set of the value.\r
632             public Value(long first, long second, int index, long cs) {\r
633                 this.first = first;\r
634                 this.second = second;\r
635                 this.index = index;\r
636                 this.cs = cs;\r
637             }\r
638             String getName() {\r
639                 return ProCoreServer.VALUE_PREFIX + toString() + ProCoreServer.VALUE_SUFFIX;\r
640             }\r
641             @Override\r
642             public String toString() {\r
643                 return String.format("%x.%x.%d.%d", first, second, index, cs);\r
644             }\r
645             @Override\r
646             public boolean equals(Object o) {\r
647                 if (this == o)\r
648                     return true;\r
649                 else if (!(o instanceof Value))\r
650                     return false;\r
651                 Value x = (Value)o;\r
652                 return first == x.first && second == x.second && index == x.index;\r
653             }\r
654             @Override\r
655             public int hashCode() {\r
656                 int result = 17;\r
657                 int f = (int)(first ^ (first >>> 32));\r
658                 result = 31 * result + f;\r
659                 int s = (int)(second ^ (second >>> 32));\r
660                 result = 31 * result + s;\r
661                 return result + index;\r
662             }\r
663         }\r
664         File[] files = folder.toFile().listFiles(new FilenameFilter() {\r
665             @Override\r
666             public boolean accept(File dir, String name) {\r
667                 return name.startsWith(ProCoreServer.VALUE_PREFIX);\r
668             }\r
669         });\r
670         HashMap<Value, Value> values = new HashMap<Value, Value>();\r
671         for (int i = 0; i < files.length; ++i) {\r
672             String s = files[i].getName();\r
673             String[] ss = s.split("\\.");\r
674             if (ss.length != 6) {\r
675                 Logger.defaultLogError("Illegal external value file name. name=" + s);\r
676                 continue;\r
677             }\r
678             long first = convertHexStringToLong(ss[1]);\r
679             long second = convertHexStringToLong(ss[2]);\r
680             int ri = Integer.parseInt(ss[3]);\r
681             long cs = Long.parseLong(ss[4]);\r
682             Value nv = new Value(first, second, ri, cs);\r
683             Value ov = values.get(nv);\r
684             if (null == ov)\r
685                 values.put(nv, nv);\r
686             else if (ov.cs < nv.cs) {\r
687                 deleteFile(folder, ov.getName(), FileOption.IF_EXIST);\r
688                 ov.cs = nv.cs;;\r
689             } else\r
690                 deleteFile(folder, nv.getName(), FileOption.IF_EXIST);\r
691         }\r
692     }\r
693     private long getNextClusterId(final Path db) throws ProCoreException {\r
694         long clusterId = 0;\r
695         Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
696         File[] files = dbHead.toFile().listFiles(new FilenameFilter() {\r
697             @Override\r
698             public boolean accept(File dir, String name) {\r
699                 return name.startsWith(ProCoreServer.DATA_PREFIX);\r
700             }\r
701         });\r
702         for (int i = 0; i < files.length; ++i) {\r
703             String s = files[i].getName();\r
704             String[] ss = s.split("\\.", 4);\r
705             if (ss.length != 4) {\r
706                 Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
707                 continue;\r
708             }\r
709             long id = convertHexStringToLong(ss[2]);\r
710             if (id > clusterId)\r
711                 clusterId = id;\r
712         }\r
713         final Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
714         if (!Files.exists(dbTail))\r
715             return clusterId + 1;\r
716         class Visitor extends SimpleFileVisitor<Path> {\r
717             private final PathMatcher matcher;\r
718             long clusterId;\r
719             Visitor(String pattern, long clusterId) {\r
720                 this.clusterId = clusterId;\r
721                 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
722             }\r
723             @Override\r
724             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
725                 Path name = file.getFileName();\r
726                 if (null == name)\r
727                     return FileVisitResult.CONTINUE;\r
728                 else if (!matcher.matches(name))\r
729                     return FileVisitResult.CONTINUE;\r
730                 String s = name.toString();\r
731                 String[] ss = s.split("\\.", 4);\r
732                 if (ss.length != 4) {\r
733                     Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
734                     return FileVisitResult.CONTINUE;\r
735                 }\r
736                 long id = convertHexStringToLong(ss[2]);\r
737                 if (id > clusterId)\r
738                     clusterId = id;\r
739                 return FileVisitResult.CONTINUE;\r
740             }\r
741             @Override\r
742             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
743                 if (dir.equals(dbTail))\r
744                     return FileVisitResult.CONTINUE;\r
745                 else\r
746                     return FileVisitResult.SKIP_SUBTREE;\r
747             }\r
748         }\r
749         try {\r
750             Visitor v = new Visitor(ProCoreServer.DATA_PATTERN, clusterId);\r
751             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
752             Files.walkFileTree(dbTail, opts, Integer.MAX_VALUE, v);\r
753             return v.clusterId + 1;\r
754         } catch (IOException e) {\r
755             throw new ProCoreException("Could not get next free cluster id for " + db, e);\r
756         }\r
757     }\r
758     private static void deleteFiles(final Path folder, String pattern) throws ProCoreException {\r
759         if (!Files.exists(folder))\r
760             return;\r
761         class Visitor extends SimpleFileVisitor<Path> {\r
762             private final PathMatcher matcher;\r
763             Visitor(String pattern) {\r
764                 matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
765             }\r
766             @Override\r
767             public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
768                 Path name = file.getFileName();\r
769                 if (null == name)\r
770                     return FileVisitResult.CONTINUE;\r
771                 else if (!matcher.matches(name))\r
772                     return FileVisitResult.CONTINUE;\r
773                 Files.delete(file);\r
774                 return FileVisitResult.CONTINUE;\r
775             }\r
776             @Override\r
777             public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
778                 if (dir.equals(folder))\r
779                     return FileVisitResult.CONTINUE;\r
780                 else\r
781                     return FileVisitResult.SKIP_SUBTREE;\r
782             }\r
783         }\r
784         try {\r
785             Visitor v = new Visitor(pattern);\r
786             EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
787             Files.walkFileTree(folder, opts, Integer.MAX_VALUE, v);\r
788         } catch (IOException e) {\r
789             throw new ProCoreException("Could not delete " + folder + " with " + pattern, e);\r
790         }\r
791     }\r
792     public void copy(File to) throws ProCoreException {\r
793         copyDatabaseFiles(server.getFolder().toPath(), to.toPath());\r
794     }\r
795     public boolean isServerAlive() {\r
796         return server.isAlive();\r
797     }\r
798     Client newClient() throws ProCoreException {\r
799         return server.newClient();\r
800     }\r
801     public void createFolder() throws ProCoreException {\r
802         if (server.folder.exists())\r
803             return;\r
804         boolean created = server.folder.mkdirs();\r
805         if (!created)\r
806             throw new ProCoreException("Could not create folder=" + server.folder);\r
807     }\r
808     /**\r
809      * @throws ProCoreException if could not stop server.\r
810      */\r
811     public boolean serverTryToStop() throws ProCoreException {\r
812         return server.tryToStop();\r
813     }\r
814     class Server {\r
815         class Command {\r
816             void Do(ProCoreServer proCoreServer) {\r
817             }\r
818         }\r
819         class Event {\r
820         }\r
821         private final File folder; // Always non null.\r
822         private final ProCoreServer proCoreServer;\r
823         void start()  throws ProCoreException {\r
824             try {\r
825                 proCoreServer.start();\r
826             } catch (DatabaseLastExitException e) {\r
827                 if (null == dbUserAgent)\r
828                     return;\r
829                 if (!dbUserAgent.handleStart(e))\r
830                     throw new ProCoreException(folder, "Failed to handle start exception.", e);\r
831                 proCoreServer.start();\r
832             }\r
833         }\r
834         void stop() throws ProCoreException {\r
835             try {\r
836                 proCoreServer.stop();\r
837             } catch (InterruptedException e) {\r
838                 if (proCoreServer.isAlive())\r
839                     throw new ProCoreException("ProCoreServer stop was interrupted.", e);\r
840             }\r
841         }\r
842         boolean isAlive() {\r
843             try {\r
844                 return proCoreServer.isAlive();\r
845             } catch (ProCoreException e) {\r
846                 Logger.defaultLogError(e);\r
847                 return false;\r
848             }\r
849         }\r
850         Server(File aFolder) {\r
851             if (null == aFolder)\r
852                 throw new RuntimeException("Database folder can not be null.");\r
853             try {\r
854                 folder = aFolder.getCanonicalFile();\r
855             } catch (IOException e) {\r
856                 String t = "Could not get canonical path. file=" + aFolder;\r
857                 Logger.defaultLogError(t);\r
858                 throw new RuntimeException(t);\r
859             }\r
860             try {\r
861                 File serverFolder = org.simantics.db.server.internal.Activator.getServerFolder();\r
862                 if (!folder.isDirectory())\r
863                     folder.mkdirs();\r
864                 proCoreServer = ProCoreServer.getProCoreServer(serverFolder, folder);\r
865                 return;\r
866             } catch (Throwable t) {\r
867                 Logger.defaultLogError(t);\r
868                 throw new RuntimeException(t);\r
869             }\r
870         }\r
871         void createConfiguration() throws ProCoreException {\r
872             if (!folder.isDirectory())\r
873                 throw new ProCoreException("Can't create configuration because folder is not ok. folder=" + folder.getAbsolutePath());\r
874             File file = new File(folder, ProCoreServer.CONFIG_FILE);\r
875             try {\r
876                 file.createNewFile();\r
877             } catch (IOException e) {\r
878                 throw new ProCoreException("Can't create configuration file. file=" + file.getAbsolutePath());\r
879             }\r
880         }\r
881         void deleteGuard() throws ProCoreException {\r
882             if (server.isActive())\r
883                 throw new ProCoreException("Will not delete guard file when server is alive.");\r
884             if (!server.isFolderOk(server.getFolder()))\r
885                 throw new ProCoreException("Will not delete guard file when server folder is not ok. folder=" + server.getFolder());\r
886             File file = new File(folder, ProCoreServer.GUARD_FILE);\r
887             if (!file.exists())\r
888                 throw new ProCoreException("Guard file does not exist. file=" + file.getAbsolutePath());\r
889             boolean deleted = file.delete();\r
890             if (!deleted)\r
891                 throw new ProCoreException("Failed to delete file=" + file.getAbsolutePath());\r
892         }\r
893         String execute(String aCommand) throws ProCoreException {\r
894             try {\r
895                 return proCoreServer.execute(aCommand);\r
896             } catch (InterruptedException e) {\r
897                 throw new ProCoreException("Execute call was interrupted.", e);\r
898             }\r
899         }\r
900         Status getStatus() {\r
901             Status status = Status.NoDatabase;\r
902             String path;\r
903             try {\r
904                 path = folder.getCanonicalPath();\r
905             } catch (IOException e) {\r
906                 Util.logError("Could not get canonical path for folder. folder=" + folder.getAbsolutePath(), e);\r
907                 path = "<no path>";\r
908             }\r
909             if (!isFolderOk(folder))\r
910                 status = Status.NoDatabase;\r
911             else if (!isAlive())\r
912                 status = Status.NotRunning;\r
913             else if (isConnected()) {\r
914                 if (isLocal())\r
915                     status = Status.Local;\r
916                 else\r
917                     status = Status.Remote;\r
918             } else\r
919                 status = Status.Standalone;\r
920             status.message = status.getString() + "@" + path;\r
921             return status;\r
922         }\r
923         File getFolder() {\r
924             return folder;\r
925         }\r
926         boolean isActive() {\r
927             try {\r
928                 return proCoreServer.isActive();\r
929             } catch (ProCoreException e) {\r
930                 Logger.defaultLogError("IsActive failed.", e);\r
931                 return false;\r
932             }\r
933         }\r
934         boolean isConnected() {\r
935             try {\r
936                 return proCoreServer.isConnected();\r
937             } catch (ProCoreException e) {\r
938                 Logger.defaultLogError("IsConnected failed.", e);\r
939                 return false;\r
940             }\r
941         }\r
942         boolean isLocal() {\r
943             try {\r
944                 return proCoreServer.isLocal();\r
945             } catch (ProCoreException e) {\r
946                 Logger.defaultLogError("IsLocal faailed.", e);\r
947                 return false;\r
948             }\r
949         }\r
950         void connect() throws ProCoreException, InterruptedException {\r
951             proCoreServer.connect();\r
952         }\r
953         void disconnect() {\r
954             try {\r
955                 proCoreServer.disconnect();\r
956             } catch (ProCoreException e) {\r
957                 Logger.defaultLogError("Could not disconnect.", e);\r
958             }\r
959         }\r
960         /**\r
961          * @param aFolder\r
962          * @return true if given folder contains database journal file or\r
963          *         configuration file.\r
964          */\r
965         boolean isFolderOk(File aFolder) {\r
966             if (!aFolder.isDirectory())\r
967                 return false;\r
968             File config = new File(aFolder, ProCoreServer.CONFIG_FILE);\r
969             if (config.exists())\r
970                 return true;\r
971             File journal = new File(aFolder, ProCoreServer.JOURNAL_FILE);\r
972             if (journal.exists())\r
973                 return true;\r
974             return false;\r
975         }\r
976         /**\r
977          * @throws ProCoreException if could not stop server.\r
978          */\r
979         boolean tryToStop() throws ProCoreException {\r
980             return proCoreServer.tryToStop();\r
981         }\r
982         Client newClient() throws ProCoreException {\r
983             return proCoreServer.newClient();\r
984         }\r
985     }\r
986 // From interface Database\r
987     @Override\r
988     public void initFolder(Properties properties) throws ProCoreException {\r
989         createFolder();\r
990         serverCreateConfiguration();\r
991     }\r
992     @Override\r
993     public void deleteFiles() throws ProCoreException {\r
994         deleteDatabaseFiles();\r
995     }\r
996     @Override\r
997     public void start() throws ProCoreException {\r
998         connect();\r
999     }\r
1000     @Override\r
1001     public boolean isRunning() throws ProCoreException {\r
1002         return isServerAlive();\r
1003     }\r
1004     @Override\r
1005     public boolean tryToStop() throws ProCoreException {\r
1006         return serverTryToStop();\r
1007     }\r
1008     @Override\r
1009     public String execute(String aCommand) throws ProCoreException {\r
1010         return server.execute(aCommand);\r
1011     }\r
1012     @Override\r
1013     public void purgeDatabase() throws SDBException {\r
1014         synchronized (server.proCoreServer.getProCoreClient()) {\r
1015             if (!server.isLocal())\r
1016                 throw new ProCoreException("Purge is allowed only for local server.");\r
1017             List<Client> clients = sessionManager.disconnect(this);\r
1018             purge();\r
1019             try {\r
1020                 sessionManager.connect(this, clients);\r
1021             } catch (InterruptedException e) {\r
1022                 throw new ProCoreException("Failed to connect after purge.", e);\r
1023             }\r
1024         }\r
1025     }\r
1026     @Override\r
1027     public Session newSession(ServiceLocator locator) throws ProCoreException {\r
1028         return sessionManager.newSession(this);\r
1029     }\r
1030     @Override\r
1031     public Path createFromChangeSets(int revision) throws ProCoreException {\r
1032         if (!isFolderOk())\r
1033             throw new ProCoreException("Folder must be valid database folder to create database from journal." );\r
1034         File folder = server.getFolder();\r
1035         File file = new File(folder, ProCoreServer.DCS_FILE);\r
1036         if (!file.isFile() && !file.canRead())\r
1037             throw new ProCoreException("Dump file must be readable. file=" + file.getAbsolutePath());\r
1038         Path db = Paths.get(folder.getAbsolutePath());\r
1039         Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from change sets.");\r
1040         Server s = new Server(temp.toFile());\r
1041         s.createConfiguration();\r
1042         s.start();\r
1043         String t = s.execute("loadChangeSets .. " + revision);\r
1044         if (t.length() < 1)\r
1045             throw new ProCoreException("Could not read journal. reply=" + t);\r
1046         s.tryToStop();\r
1047         try {\r
1048             int cs = Integer.parseInt(t);\r
1049             if (cs == revision)\r
1050                 return temp;\r
1051             throw new ProCoreException("Could not load change sets. reply=" + t);\r
1052         } catch (NumberFormatException e) {\r
1053             throw new ProCoreException("Could not load change sets. reply=" + t);\r
1054         }\r
1055     }\r
1056     @Override\r
1057     public void clone(File to, int revision, boolean saveHistory) {\r
1058         String history = saveHistory ? "with history." : "without history.";\r
1059         String message = "Clone to " + to.getAbsolutePath() + "@" + revision + " " + history;\r
1060         Util.trace(message);\r
1061     }\r
1062     @Override\r
1063     public DatabaseUserAgent getUserAgent() {\r
1064         return dbUserAgent;\r
1065     }\r
1066     @Override\r
1067     public void setUserAgent(DatabaseUserAgent dbUserAgent) {\r
1068         this.dbUserAgent = dbUserAgent;\r
1069     }\r
1070     @Override\r
1071     public Status getStatus() {\r
1072         return server.getStatus();\r
1073     }\r
1074     @Override\r
1075     public File getFolder() {\r
1076         return server.getFolder();\r
1077     }\r
1078     @Override\r
1079     public boolean isFolderOk() {\r
1080         return server.isFolderOk(server.getFolder());\r
1081     }\r
1082     @Override\r
1083     public boolean isFolderOk(File folder) {\r
1084         return server.isFolderOk(folder);\r
1085     }\r
1086     public boolean isFolderEmpty() {\r
1087         return isFolderEmpty(server.getFolder());\r
1088     }\r
1089     @Override\r
1090     public boolean isFolderEmpty(File folder) {\r
1091         return isFolderEmpty(folder.toPath());\r
1092     }\r
1093     @Override\r
1094     public void deleteGuard() throws ProCoreException {\r
1095         server.deleteGuard();\r
1096     }\r
1097     @Override\r
1098     public boolean isConnected() throws ProCoreException {\r
1099         return server.isConnected();\r
1100     }\r
1101     @Override\r
1102     public void connect() throws ProCoreException {\r
1103         if (!isFolderOk())\r
1104             throw new ProCoreException("Could not connect to " + getFolder());\r
1105         if (!server.isAlive())\r
1106             server.start();\r
1107         if (isConnected())\r
1108             return;\r
1109         try {\r
1110             server.connect();\r
1111         } catch (InterruptedException e) {\r
1112             Util.logError("Server connect was interrupted.", e);\r
1113         }\r
1114         if (server.isActive())\r
1115             return;\r
1116         throw new ProCoreException("Could not connect to " + getFolder());\r
1117     }\r
1118     @Override\r
1119     public void disconnect() throws ProCoreException {\r
1120         server.disconnect();\r
1121     }\r
1122     @Override\r
1123     public Path dumpChangeSets() throws ProCoreException {\r
1124         if (!isFolderOk())\r
1125             throw new ProCoreException("Folder must be set to dump change sets.");\r
1126         if (!server.isActive())\r
1127             throw new  ProCoreException("Server must be responsive to dump change sets.");\r
1128         String t = server.execute("dumpChangeSets").replaceAll("\n", "");\r
1129         try {\r
1130             int ncs = Integer.parseInt(t);\r
1131             if (ncs < 1)\r
1132                 return null;\r
1133             File file = new File(getFolder(), ProCoreServer.DCS_FILE);\r
1134             return file.toPath();\r
1135         } catch (NumberFormatException e) {\r
1136             throw new ProCoreException("Could not dump change sets.", e);\r
1137         }\r
1138     }\r
1139     @Override\r
1140     public long serverGetTailChangeSetId() throws ProCoreException {\r
1141         try {\r
1142             return server.proCoreServer.getTailData().nextChangeSetId;\r
1143         } catch (TailReadException e) {\r
1144             return 1;\r
1145         }\r
1146     }\r
1147     @Override\r
1148     public JournalI getJournal() throws ProCoreException {\r
1149         return journal;\r
1150     }\r
1151     class JournalI implements Database.Journal {\r
1152         class Analyzed {\r
1153             private final File journalFile;\r
1154             private long lastModified;\r
1155             private RandomAccessFile file; // Journal file.\r
1156             private ArrayList<Long> offsets = new ArrayList<Long>(); // Offsets to commands in journal file.\r
1157             private Line lines[] = new Line[256];\r
1158             int firstLine = 0; // Index of the first element in lines table.\r
1159             Analyzed(File dbFolder) {\r
1160                 journalFile = new File(dbFolder, ProCoreServer.JOURNAL_FILE);\r
1161                 for (int i=0; i<lines.length; ++i)\r
1162                     lines[i] = new Line();\r
1163             }\r
1164             private boolean canRead() {\r
1165                 return journalFile.isFile() && journalFile.canRead();\r
1166             }\r
1167             private int count() {\r
1168                 try {\r
1169                     return readOffsets().size();\r
1170                 } catch (ProCoreException e) {\r
1171                     Util.logError("Failed to read or analyze journal. file=" + journalFile, e);\r
1172                     clear();\r
1173                     return 0;\r
1174                 }\r
1175             }\r
1176             private void clear() {\r
1177                 close();\r
1178                 offsets.clear();\r
1179             }\r
1180             private void close() {\r
1181                 if (file != null) {\r
1182                     try {\r
1183                         file.close();\r
1184                     } catch (Throwable e) {\r
1185                         Logger.defaultLogError("Close file threw an exception.", e);\r
1186                     }\r
1187                     file = null;\r
1188                 }\r
1189             }\r
1190             private void open() throws ProCoreException {\r
1191                 try {\r
1192                     file = new RandomAccessFile(journalFile, "r");\r
1193                 } catch (Throwable e) {\r
1194                     file = null;\r
1195                     throw new ProCoreException("Failed to open journal file.", e);\r
1196                 }\r
1197             }\r
1198             private String getComment(byte[] bytes) throws ProCoreException {\r
1199                 Serializer METADATA_SERIALIZER = Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.BYTE_ARRAY));\r
1200                 try {\r
1201                     @SuppressWarnings("unchecked")\r
1202                     TreeMap<String, byte[]> metadata = (TreeMap<String, byte[]>) METADATA_SERIALIZER.deserialize(bytes);\r
1203                     CommitMetadata commit = MetadataUtil.getMetadata(metadata, CommitMetadata.class);\r
1204                     String date = "<metadata does not contain date>";\r
1205                     if (null != commit  && null != commit.date)\r
1206                         date = commit.date.toString();\r
1207                     CommentMetadata comment = MetadataUtil.getMetadata(metadata, CommentMetadata.class);\r
1208                     String comments = "<metadata does not contain comment>";\r
1209                     if (null != comment)\r
1210                         comments = comment.toString();\r
1211                     return date + " " + comments;\r
1212                 } catch (IOException e) {\r
1213                     throw new ProCoreException("Failed to interpret metadata.", e);\r
1214                 }\r
1215             }\r
1216             private Line getRow(int index, Line line) throws ProCoreException {\r
1217                 if (index < 0 || index >= offsets.size())\r
1218                     return null; // Index out of range.\r
1219                 if (index < firstLine || index >= firstLine + lines.length) {\r
1220                     readLines(index);\r
1221                     if (index < firstLine)\r
1222                         return null; // Index out of range.\r
1223                 }\r
1224                 int offset = index - firstLine;\r
1225                 line.status = lines[offset].status;\r
1226                 line.request = lines[offset].request;\r
1227                 line.comment = lines[offset].comment;\r
1228                 return line;\r
1229             }\r
1230             private void readLines(int first) throws ProCoreException {\r
1231                 open();\r
1232                 try {\r
1233                     for (int i=0, index = first; i<lines.length; ++i, ++index)\r
1234                         readLine(index, lines[i]);\r
1235                     firstLine = first;\r
1236                 } finally {\r
1237                     close();\r
1238                 }\r
1239             }\r
1240             private void readLine(int index, Line line) throws ProCoreException {\r
1241                 if (index >= offsets.size()) {\r
1242                     line.status = false;\r
1243                     line.request = "<Illegal request.>";\r
1244                     line.comment = "<Illegal request.>";\r
1245                     return;\r
1246                 }\r
1247                 long offset = offsets.get(index);\r
1248                 try {\r
1249                     file.seek(offset);\r
1250                     int i = file.readInt();\r
1251                     int length = Integer.reverseBytes(i);\r
1252                     byte b = file.readByte();\r
1253                     boolean ok = b != 0;\r
1254                     b = file.readByte();\r
1255                     // boolean littleEndian = b != 0;\r
1256                     i = file.readInt();\r
1257                     int type = Integer.reverseBytes(i);\r
1258                     String comment = "";\r
1259                     if (length < 6)\r
1260                         throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
1261                     else if (length > 6) {\r
1262                         if (type != 3)\r
1263                             file.skipBytes(length - 6);\r
1264                         else if (length > 22){\r
1265                             file.skipBytes(16);\r
1266                             i = file.readInt();\r
1267                             int size = Integer.reverseBytes(i);\r
1268                             if (size != length - 26)\r
1269                                 throw new ProCoreException("Metadata corrupted at" + file.getFilePointer() + ".");\r
1270                             byte[] bytes = new byte[size];\r
1271                             file.readFully(bytes);\r
1272                             comment = getComment(bytes);\r
1273                             if (null == comment)\r
1274                                 comment = "<metadata does not contain comment>";\r
1275                         }\r
1276                     }\r
1277                     i = file.readInt();\r
1278                     int length2 = Integer.reverseBytes(i);\r
1279                     if (length != length2)\r
1280                         throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
1281                     String command = MessageText.get(type);\r
1282                     line.status = ok;\r
1283                     line.request = command;\r
1284                     line.comment = comment;\r
1285                 } catch (IOException e) {\r
1286                     throw new ProCoreException("Journal file corrupted.");\r
1287                 }\r
1288             }\r
1289             private ArrayList<Long> readOffsets() throws ProCoreException {\r
1290                 if (!canRead()) {\r
1291                     lastModified = 0;\r
1292                     offsets.clear();\r
1293                     firstLine = 0;\r
1294                     return offsets;\r
1295                 }\r
1296                 long modified = journalFile.lastModified();\r
1297                 if (lastModified != 0 && lastModified == modified)\r
1298                     return offsets; // Offsets already up to date.\r
1299                 lastModified = 0;\r
1300                 offsets.clear();\r
1301                 firstLine = 0;\r
1302                 open();\r
1303                 try {\r
1304                     file.seek(0);\r
1305                     int i = file.readInt();\r
1306                     int version = Integer.reverseBytes(i);\r
1307                     if (version != 1)\r
1308                         throw new ProCoreException("Unsupported journal file version. expected=1 got=" + version);\r
1309                     i = file.readInt();\r
1310                     int major = Integer.reverseBytes(i);\r
1311                     if (major != MessageNumber.ProtocolVersionMajor)\r
1312                         throw new ProCoreException("Unsupported journal request major version. expected=" + MessageNumber.ProtocolVersionMajor + " got=" + major);\r
1313                     i = file.readInt();\r
1314                     int minor = Integer.reverseBytes(i);\r
1315                     if (minor > MessageNumber.ProtocolVersionMinor)\r
1316                         throw new ProCoreException("Unsupported journal request minor version. expected=" + MessageNumber.ProtocolVersionMinor + " got=" + minor);\r
1317                     i = file.readInt();\r
1318                     int length = Integer.reverseBytes(i);\r
1319                     while (length > 0) { // Not supporting unsigned integers.\r
1320                         if (length < 6)\r
1321                             throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
1322                         file.skipBytes(length);\r
1323                         i = file.readInt();\r
1324                         int length2 = Integer.reverseBytes(i);\r
1325                         if (length != length2)\r
1326                             throw new ProCoreException("Journal file corrupted at " + file.getFilePointer() + ".");\r
1327                         long offset = file.getFilePointer() - length - 8;\r
1328                         offsets.add(offset);\r
1329                         i = file.readInt();\r
1330                         length = Integer.reverseBytes(i);\r
1331                     }\r
1332                 } catch (EOFException e) {\r
1333                 } catch (IOException e) {\r
1334                     throw new ProCoreException("Failed to get command count.", e);\r
1335                 } finally {\r
1336                     close();\r
1337                 }\r
1338                 lastModified = modified;\r
1339                 readLines(0);\r
1340                 return offsets;\r
1341             }\r
1342         }\r
1343         private final Analyzed analyzed;\r
1344         JournalI(File dbFolder) {\r
1345             this.analyzed = new Analyzed(dbFolder);\r
1346         }\r
1347         @Override\r
1348         public boolean canRead() {\r
1349             return analyzed.canRead() && !isServerAlive();\r
1350         }\r
1351         @Override\r
1352         public int count() {\r
1353             return analyzed.count();\r
1354         }\r
1355         @Override\r
1356         public int read(int index, Line line) throws ProCoreException {\r
1357             int count = analyzed.count();\r
1358             analyzed.getRow(index, line);\r
1359             return count;\r
1360         }\r
1361     }\r
1362         @Override\r
1363         public String getCompression() {\r
1364                 return "FLZ";\r
1365         }\r
1366 }\r