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