--- /dev/null
+package org.simantics.db.server.internal;\r
+\r
+import static java.nio.file.StandardCopyOption.COPY_ATTRIBUTES;\r
+import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;\r
+\r
+import java.io.EOFException;\r
+import java.io.File;\r
+import java.io.FilenameFilter;\r
+import java.io.IOException;\r
+import java.io.OutputStream;\r
+import java.io.RandomAccessFile;\r
+import java.nio.file.DirectoryStream;\r
+import java.nio.file.FileSystems;\r
+import java.nio.file.FileVisitOption;\r
+import java.nio.file.FileVisitResult;\r
+import java.nio.file.Files;\r
+import java.nio.file.Path;\r
+import java.nio.file.PathMatcher;\r
+import java.nio.file.Paths;\r
+import java.nio.file.SimpleFileVisitor;\r
+import java.nio.file.attribute.BasicFileAttributes;\r
+import java.util.ArrayList;\r
+import java.util.EnumSet;\r
+import java.util.HashMap;\r
+import java.util.List;\r
+import java.util.Properties;\r
+import java.util.TreeMap;\r
+import java.util.UUID;\r
+\r
+import org.simantics.databoard.Bindings;\r
+import org.simantics.databoard.binding.impl.TreeMapBinding;\r
+import org.simantics.databoard.serialization.Serializer;\r
+import org.simantics.db.Database;\r
+import org.simantics.db.DatabaseUserAgent;\r
+import org.simantics.db.ServiceLocator;\r
+import org.simantics.db.common.CommentMetadata;\r
+import org.simantics.db.common.CommitMetadata;\r
+import org.simantics.db.common.utils.Logger;\r
+import org.simantics.db.common.utils.MetadataUtil;\r
+import org.simantics.db.exception.SDBException;\r
+import org.simantics.db.server.DatabaseLastExitException;\r
+import org.simantics.db.server.ProCoreException;\r
+import org.simantics.db.server.internal.ProCoreServer.TailFile;\r
+import org.simantics.db.server.protocol.MessageNumber;\r
+import org.simantics.db.server.protocol.MessageText;\r
+\r
+public class DatabaseI implements Database {\r
+ private static String NL = System.getProperty("line.separator");\r
+ private static String TEMP_PREFIX = "db.temp.";\r
+ public static DatabaseI newDatabaseI(File dbFolder) {\r
+ return new DatabaseI(dbFolder);\r
+ }\r
+ private final SessionManager sessionManager = new SessionManager();\r
+ private final Server server;\r
+ private final JournalI journal;\r
+ private DatabaseUserAgent dbUserAgent = null;\r
+ private DatabaseI(File dbFolder) {\r
+ server = new Server(dbFolder);\r
+ journal = new JournalI(dbFolder);\r
+ }\r
+ public boolean isSubFolder(Path base, Path sub)\r
+ throws IOException {\r
+ if (null == base || null == sub)\r
+ return false;\r
+ return isSubFolder(base.toFile(), sub.toFile());\r
+ }\r
+ public boolean isSubFolder(File base, File sub)\r
+ throws IOException {\r
+ if (null == base || null == sub)\r
+ return false;\r
+ Path basePath = base.getCanonicalFile().toPath();\r
+ Path subPath = sub.getCanonicalFile().toPath();\r
+ if (subPath.startsWith(basePath))\r
+ return true;\r
+ else\r
+ return false;\r
+ }\r
+ public Path saveDatabase()\r
+ throws ProCoreException {\r
+ Path dbFolder = getFolder().toPath();\r
+ Path parent = getFolder().getParentFile().toPath();\r
+ Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
+ copyTree(dbFolder, temp, FileOption.IF_NEW);\r
+ return temp;\r
+ }\r
+ public Path saveDatabase(Path parent)\r
+ throws ProCoreException {\r
+ Path dbFolder = getFolder().toPath();\r
+ try {\r
+ boolean yes = isSubFolder(dbFolder, parent);\r
+ if (yes)\r
+ throw new ProCoreException("Parent must not be subdirectory of database directory."\r
+ + NL + "database=" + dbFolder\r
+ + NL + "parent=" + parent);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Failed to save database to parent."\r
+ + NL + "database=" + dbFolder\r
+ + NL + "parent=" + parent);\r
+ }\r
+ Path temp = createTempFolder(parent, "Could not create temporary directory for saving database.");\r
+ copyTree(dbFolder, temp, FileOption.IF_NEW);\r
+ return temp;\r
+ }\r
+ public void deleteDatabaseFiles() throws ProCoreException {\r
+ if (server.isAlive()) {\r
+ server.tryToStop();\r
+ if (server.isAlive())\r
+ throw new ProCoreException("Server must be dead to delete database.");\r
+ }\r
+ Path db = getFolder().toPath();\r
+ deleteDatabaseFiles(db); // Redundant, but tests the method.\r
+ deleteTree(db);\r
+ }\r
+ Path copyDatabaseFiles(Path from, Path to) throws ProCoreException {\r
+ copyTree(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_EXIST);\r
+ if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
+ copyTree(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_EXIST);\r
+ copyPath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
+ copyPath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+ copyPath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+ copyPath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+ copyFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_EXIST);\r
+ return to;\r
+ }\r
+ public static void deleteDatabaseFiles(Path from) throws ProCoreException {\r
+ deleteFile(from, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+ deleteFile(from, ProCoreServer.CONFIG_FILE, FileOption.IF_EXIST);\r
+ deleteFiles(from, ProCoreServer.PAGE_FILE_PATTERN);\r
+ deleteTree(from.resolve(ProCoreServer.BRANCH_DIR));\r
+ deleteFile(from, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+ deleteTree(from.resolve(ProCoreServer.TAIL_DIR));\r
+ }\r
+ Path moveDatabaseFiles(Path from, Path to) throws ProCoreException {\r
+ moveFolder(from, to, ProCoreServer.BRANCH_DIR, FileOption.IF_NEW);\r
+ if (Files.exists(from.resolve(ProCoreServer.TAIL_DIR)))\r
+ moveFolder(from, to, ProCoreServer.TAIL_DIR, FileOption.IF_NEW);\r
+ movePath(from, to, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
+ movePath(from, to, ProCoreServer.GUARD_FILE, FileOption.IF_NEW);\r
+ movePath(from, to, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
+ movePath(from, to, ProCoreServer.LOG_FILE, FileOption.IF_NEW);\r
+ moveFiles(from, to, ProCoreServer.PAGE_FILE_PATTERN, FileOption.IF_NEW);\r
+ movePath(from, to, ProCoreServer.DCS_FILE, FileOption.IF_NEW);\r
+ moveFile(from, to, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_NEW);\r
+ moveFile(from, to, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_NEW);\r
+ moveFile(from, to, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_NEW);\r
+ return to;\r
+ }\r
+ private void moveFile(Path from, Path to, String file, FileOption fileOption) throws ProCoreException {\r
+ if (Files.exists(from.resolve(file)))\r
+ movePath(from, to, file, fileOption);\r
+ }\r
+ public void serverCreateConfiguration() throws ProCoreException {\r
+ server.createConfiguration();\r
+ }\r
+ public boolean ignoreExit() throws ProCoreException {\r
+ if (!isFolderOk())\r
+ throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
+ server.deleteGuard();\r
+ File folder = server.getFolder();\r
+ Path db = Paths.get(folder.getAbsolutePath());\r
+ Path ri = db.resolve(ProCoreServer.RECOVERY_IGNORED_FILE);\r
+ if (!Files.exists(ri))\r
+ try (OutputStream os = Files.newOutputStream(ri)) {\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not create file: " + ri, e);\r
+ }\r
+ boolean ok = false;\r
+ DatabaseUserAgent dbu = dbUserAgent; // Save current user agent.\r
+ try {\r
+ dbUserAgent = null; // Recursion not supported. Disable user agent.\r
+ server.start();\r
+ ok = server.isActive();\r
+ } finally {\r
+ try {\r
+ dbUserAgent = dbu; // Restore used user agent.\r
+ server.stop();\r
+ } finally {\r
+ try {\r
+ Files.deleteIfExists(ri);\r
+ } catch (IOException e) {\r
+ Logger.defaultLogError("Could not delete file: " + ri, e);\r
+ }\r
+ Path rn = db.resolve(ProCoreServer.RECOVERY_NEEDED_FILE);\r
+ try {\r
+ Files.deleteIfExists(rn);\r
+ } catch (IOException e) {\r
+ Logger.defaultLogError("Could not delete file: " + rn, e);\r
+ }\r
+ }\r
+ }\r
+ return ok;\r
+ }\r
+ public boolean ignoreProtocol() throws ProCoreException {\r
+ if (!isFolderOk())\r
+ throw new ProCoreException("Folder must be valid database folder to ignore exit." );\r
+ File folder = server.getFolder();\r
+ Path db = Paths.get(folder.getAbsolutePath());\r
+ Path ri = db.resolve(ProCoreServer.PROTOCOL_IGNORED_FILE);\r
+ if (!Files.exists(ri))\r
+ try (OutputStream os = Files.newOutputStream(ri)) {\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not create file: " + ri, e);\r
+ }\r
+ boolean ok = false;\r
+ try {\r
+ server.start();\r
+ ok = server.isActive();\r
+ } finally {\r
+ server.stop();\r
+ }\r
+ return ok;\r
+ }\r
+ private long preJournalCheck() throws ProCoreException {\r
+ File folder = server.getFolder();\r
+ if (!folder.isDirectory())\r
+ throw new ProCoreException("Database folder does not exist." + NL + "folder=" + folder);\r
+ File file = new File(folder, ProCoreServer.JOURNAL_FILE);\r
+ if (!file.isFile())\r
+ throw new ProCoreException("Journal file does not exist." + NL + "file=" + file);\r
+ else if (!file.canRead())\r
+ throw new ProCoreException("Journal file must be readale to create database from journal." + NL + "file=" + file);\r
+ else if (server.isAlive())\r
+ throw new ProCoreException("Server must be dead to create database from journal file." + NL + "file=" + file);\r
+ return getNextClusterId(folder.toPath());\r
+ }\r
+ private void postJournalFix(long nextFreeId) throws SDBException {\r
+ Session s = newSession(null);\r
+ long current = s.reserveIds(0);\r
+ if (current < nextFreeId)\r
+ s.reserveIds((int)(nextFreeId - current));\r
+ s.isClosed();\r
+ }\r
+ private Path createTempFolder(Path parent, String fail) throws ProCoreException {\r
+ try {\r
+ return Files.createTempDirectory(parent, TEMP_PREFIX);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException(fail, e);\r
+ }\r
+ }\r
+ public void replaceFromJournal() throws SDBException {\r
+ long nextFreeId = preJournalCheck();\r
+ Path db = Paths.get(server.getFolder().getAbsolutePath());\r
+ Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from journal.");\r
+ movePath(db, temp, ProCoreServer.CONFIG_FILE, FileOption.IF_NEW);\r
+ movePath(db, temp, ProCoreServer.JOURNAL_FILE, FileOption.IF_NEW);\r
+ deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+ deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
+ Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+ deleteFiles(dbHead, ProCoreServer.VALUE_PATTERN);\r
+ deleteFiles(dbHead, ProCoreServer.DATA_PATTERN);\r
+ deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
+ Path dbHeadTailFile = dbHead.resolve(ProCoreServer.TAIL_FILE);\r
+ boolean headTailFileExisted = Files.exists(dbHeadTailFile);\r
+ final long NEXT_REVISION = 1;\r
+ if (!headTailFileExisted) // Number of change sets and database id not known at this point. Fortunately they are not used by recovery.\r
+ TailFile.createTailFile(dbHeadTailFile.toFile(), NEXT_REVISION, nextFreeId, UUID.randomUUID().toString());\r
+ movePath(dbHead, temp, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+ Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+ boolean tailExisted = Files.isDirectory(dbTail);\r
+ if (tailExisted)\r
+ copyPath(dbTail, dbHead, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+ else {\r
+ try {\r
+ Files.createDirectory(dbTail);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
+ }\r
+ copyPath(temp, dbTail, ProCoreServer.TAIL_FILE, FileOption.IF_NEW);\r
+ }\r
+ server.createConfiguration();\r
+ server.start();\r
+ try {\r
+ String t = server.execute("journalRead " + temp.getFileName());\r
+ if (t.length() > 0)\r
+ throw new ProCoreException("Could not read journal. reply=" + t);\r
+ postJournalFix(nextFreeId); // Not the right way for implementing this, but better than incorrect recovery.\r
+ } finally {\r
+ server.tryToStop();\r
+ }\r
+ movePath(temp, db, ProCoreServer.CONFIG_FILE, FileOption.OVERWRITE);\r
+ deleteTree(temp);\r
+ if (!tailExisted)\r
+ deleteTree(dbTail);\r
+ }\r
+ public boolean canPurge() throws ProCoreException {\r
+ File folder = server.getFolder();\r
+ Path db = Paths.get(folder.getAbsolutePath());\r
+ Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+ if (!Files.exists(dbHead))\r
+ return false; // Already clean.\r
+ boolean empty = isFolderEmpty(dbHead);\r
+ if (empty)\r
+ return false; // Already clean.\r
+ final boolean[] found = new boolean[1];\r
+ found[0] = false;\r
+ dbHead.toFile().listFiles(new FilenameFilter() {\r
+ @Override\r
+ public boolean accept(File dir, String name) {\r
+ if (found[0])\r
+ return false;\r
+ else if (!name.equals(ProCoreServer.TAIL_FILE)) {\r
+ found[0] = true;\r
+ return false;\r
+ }\r
+ return true;\r
+ }\r
+ });\r
+ return found[0];\r
+ }\r
+ /**\r
+ * Removes old history i.e. change sets.\r
+ * Note that also cleans last exit status and removes guard file.\r
+ * This means that all information about last exit status is also lost\r
+ * .\r
+ * @throws ProCoreException\r
+ */\r
+ public void purge() throws SDBException {\r
+ synchronized (server.proCoreServer.getProCoreClient()) {\r
+ boolean wasAlive = server.isAlive();\r
+ if (wasAlive)\r
+ server.stop();\r
+ Path db = Paths.get(server.getFolder().getAbsolutePath());\r
+ Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+ Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+ if (!Files.isDirectory(dbTail)) {\r
+ try {\r
+ Files.createDirectory(dbTail);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Failed to create directory: " + dbTail, e);\r
+ }\r
+ }\r
+ long nextFreeId = getNextClusterId(db);\r
+ boolean cleanHead = Files.isDirectory(dbHead) && !isFolderEmpty(dbHead);\r
+ Logger.defaultLog("Purging old history and exit information. folder=" + db);\r
+ if (cleanHead) {\r
+ deleteClusters(dbHead, dbTail);\r
+ movePath(dbHead, dbTail, ProCoreServer.TAIL_FILE, FileOption.OVERWRITE);\r
+ moveFiles(dbHead, dbTail, ProCoreServer.VALUE_PATTERN, FileOption.IF_NEW);\r
+ moveFiles(dbHead, dbTail, ProCoreServer.DATA_PATTERN, FileOption.OVERWRITE);\r
+ deleteFiles(dbHead, ProCoreServer.INDEX_PATTERN);\r
+ }\r
+ deleteFiles(db, ProCoreServer.PAGE_FILE_PATTERN);\r
+ deleteFile(db, ProCoreServer.JOURNAL_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.LOG_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.DCS_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.RECOVERY_NEEDED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.RECOVERY_IGNORED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.PROTOCOL_IGNORED_FILE, FileOption.IF_EXIST);\r
+ deleteFile(db, ProCoreServer.GUARD_FILE, FileOption.IF_EXIST);\r
+ purgeValues(dbTail);\r
+ server.start();\r
+ Session s = newSession(null);\r
+ long current = s.reserveIds(0);\r
+ if (current < nextFreeId)\r
+ s.reserveIds((int)(nextFreeId - current));\r
+ s.isClosed();\r
+ if (!wasAlive)\r
+ server.tryToStop();\r
+ }\r
+ }\r
+ private void copyPath(Path fromFolder, Path toFolder, String file, FileOption fileOption) throws ProCoreException {\r
+ Path from = fromFolder.resolve(file);\r
+ Path to = toFolder.resolve(file);\r
+ try {\r
+ if (FileOption.IF_EXIST.equals(fileOption)) {\r
+ if (!Files.exists(from))\r
+ return;\r
+ }\r
+ if (FileOption.OVERWRITE.equals(fileOption))\r
+ Files.copy(from, to, COPY_ATTRIBUTES, REPLACE_EXISTING);\r
+ else\r
+ Files.copy(from, to, COPY_ATTRIBUTES);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+ }\r
+ }\r
+ private static void deleteFile(Path from, String file, FileOption fileOption) throws ProCoreException {\r
+ Path path = from.resolve(file);\r
+ deletePath(path, fileOption);\r
+ }\r
+ private static void deletePath(Path path, FileOption fileOption) throws ProCoreException {\r
+ try {\r
+ if (FileOption.IF_EXIST.equals(fileOption))\r
+ Files.deleteIfExists(path);\r
+ else\r
+ Files.delete(path);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not delete " + path, e);\r
+ }\r
+ }\r
+ private boolean isFolderEmpty(final Path folder) { // True if folder exists and is empty.\r
+ if (!Files.isDirectory(folder))\r
+ return false;\r
+ try(DirectoryStream<Path> folderStream = Files.newDirectoryStream(folder)) {\r
+ return !folderStream.iterator().hasNext();\r
+ } catch (IOException e) {\r
+ Logger.defaultLogError("Failed to open folder stream. folder=" + folder, e);\r
+ return false;\r
+ }\r
+ }\r
+ private void movePath(Path fromPath, Path toPath, String file, FileOption fileOption) throws ProCoreException {\r
+ Path from = fromPath.resolve(file);\r
+ Path to = toPath.resolve(file);\r
+ try {\r
+ if (FileOption.OVERWRITE.equals(fileOption))\r
+ Files.move(from, to, REPLACE_EXISTING);\r
+ else\r
+ Files.move(from, to);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not move " + from + " to " + to, e);\r
+ }\r
+ }\r
+ private static void copyTree(Path from, Path to, String path, final FileOption fileOption) throws ProCoreException {\r
+ copyTree(from.resolve(path), to.resolve(path), fileOption);\r
+ }\r
+ private static void copyTree(Path from, Path to, final FileOption fileOption) throws ProCoreException {\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private Path fromPath;\r
+ private Path toPath;\r
+ Visitor(Path from, Path to) {\r
+ fromPath = from;\r
+ toPath = to;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ Path targetPath = toPath.resolve(fromPath.relativize(dir));\r
+ if (!Files.exists(targetPath)) {\r
+ Files.createDirectory(targetPath);\r
+ }\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ if (FileOption.OVERWRITE.equals(fileOption))\r
+ Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES, REPLACE_EXISTING);\r
+ else\r
+ Files.copy(file, toPath.resolve(fromPath.relativize(file)), COPY_ATTRIBUTES);\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(from, to);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+ }\r
+ }\r
+ private static void deleteTree(Path path) throws ProCoreException {\r
+ if (!Files.exists(path))\r
+ return;\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ try {\r
+ Files.delete(file);\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ throw ioe;\r
+ }\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult postVisitDirectory(Path dir, IOException e) throws IOException {\r
+ if (e == null) {\r
+ try {\r
+ Files.delete(dir);\r
+ } catch (IOException ioe) {\r
+ ioe.printStackTrace();\r
+ throw ioe;\r
+ }\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ throw e;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor();\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(path, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not delete " + path, e);\r
+ }\r
+ }\r
+ private static void moveFolder(Path fromPath, Path toPath, String folder, FileOption fileOption) throws ProCoreException {\r
+ Path from = fromPath.resolve(folder);\r
+ Path to = toPath.resolve(folder);\r
+ copyTree(from, to, fileOption);\r
+ deleteTree(from);\r
+ }\r
+ enum FileOption {\r
+ IF_EXIST,\r
+ IF_NEW,\r
+ OVERWRITE\r
+ }\r
+ private void copyFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private final PathMatcher matcher;\r
+ Visitor(String pattern) {\r
+ matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ Path name = file.getFileName();\r
+ if (null == name)\r
+ return FileVisitResult.CONTINUE;\r
+ else if (!matcher.matches(name))\r
+ return FileVisitResult.CONTINUE;\r
+ if (FileOption.OVERWRITE.equals(fileOption))\r
+ Files.copy(file, to.resolve(name), REPLACE_EXISTING);\r
+ else\r
+ Files.copy(file, to.resolve(name));\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ if (dir.equals(from))\r
+ return FileVisitResult.CONTINUE;\r
+ else\r
+ return FileVisitResult.SKIP_SUBTREE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(pattern);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not copy " + from + " to " + to, e);\r
+ }\r
+ }\r
+ private void moveFiles(final Path from, final Path to, String pattern, final FileOption fileOption) throws ProCoreException {\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private final PathMatcher matcher;\r
+ Visitor(String pattern) {\r
+ matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ Path name = file.getFileName();\r
+ if (null == name)\r
+ return FileVisitResult.CONTINUE;\r
+ else if (!matcher.matches(name))\r
+ return FileVisitResult.CONTINUE;\r
+ if (FileOption.OVERWRITE.equals(fileOption))\r
+ Files.move(file, to.resolve(name), REPLACE_EXISTING);\r
+ else\r
+ Files.move(file, to.resolve(name));\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ if (dir.equals(from))\r
+ return FileVisitResult.CONTINUE;\r
+ else\r
+ return FileVisitResult.SKIP_SUBTREE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(pattern);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(from, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not move " + from + " to " + to, e);\r
+ }\r
+ }\r
+ private void deleteClusters(final Path head, final Path tail) throws ProCoreException {\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private final PathMatcher matcher;\r
+ Visitor(String pattern) {\r
+ matcher = FileSystems.getDefault().getPathMatcher("glob:" + ProCoreServer.DELETED_PATTERN);\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ Path name = file.getFileName();\r
+ if (null == name)\r
+ return FileVisitResult.CONTINUE;\r
+ else if (!matcher.matches(name))\r
+ return FileVisitResult.CONTINUE;\r
+ String deletedStr = name.toString();\r
+ String indexName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.INDEX_PREFIX);\r
+ String dataName = deletedStr.replaceFirst(ProCoreServer.DELETED_PREFIX, ProCoreServer.DATA_PREFIX);\r
+ String[] ss = deletedStr.split("\\.");\r
+ String valuePattern = ProCoreServer.VALUE_PREFIX + ss[1] + "." + ss[2] + "*";\r
+ Files.delete(file);\r
+ Files.delete(head.resolve(indexName));\r
+ Files.delete(head.resolve(dataName));\r
+ try {\r
+ deleteFiles(head, valuePattern);\r
+ if (Files.isDirectory(tail)) {\r
+ Files.deleteIfExists(tail.resolve(dataName));\r
+ deleteFiles(tail, valuePattern);\r
+ }\r
+ } catch (ProCoreException e) {\r
+ throw new IOException("Delete values failed.", e);\r
+ }\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ if (dir.equals(head))\r
+ return FileVisitResult.CONTINUE;\r
+ else\r
+ return FileVisitResult.SKIP_SUBTREE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(ProCoreServer.DELETED_PREFIX);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(head, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not delete cluster(s).\nhead=" + head + "\ntail=" + tail);\r
+ }\r
+ }\r
+ private static long convertHexStringToLong(String hex) {\r
+ if (hex.length() < 16)\r
+ return Long.parseLong(hex, 16);\r
+ long f = Long.parseLong(hex.substring(0,1), 16) << 60;\r
+ long l = Long.parseLong(hex.substring(1,16), 16);\r
+ return f | l;\r
+ }\r
+ private void purgeValues(final Path folder) throws ProCoreException {\r
+ final class Value {\r
+ long first; // First part of cluster id.\r
+ long second; // Secon part of cluster id.\r
+ int index; // Resource index of the value.\r
+ transient long cs; // Change set of the value.\r
+ public Value(long first, long second, int index, long cs) {\r
+ this.first = first;\r
+ this.second = second;\r
+ this.index = index;\r
+ this.cs = cs;\r
+ }\r
+ String getName() {\r
+ return ProCoreServer.VALUE_PREFIX + toString() + ProCoreServer.VALUE_SUFFIX;\r
+ }\r
+ @Override\r
+ public String toString() {\r
+ return String.format("%x.%x.%d.%d", first, second, index, cs);\r
+ }\r
+ @Override\r
+ public boolean equals(Object o) {\r
+ if (this == o)\r
+ return true;\r
+ else if (!(o instanceof Value))\r
+ return false;\r
+ Value x = (Value)o;\r
+ return first == x.first && second == x.second && index == x.index;\r
+ }\r
+ @Override\r
+ public int hashCode() {\r
+ int result = 17;\r
+ int f = (int)(first ^ (first >>> 32));\r
+ result = 31 * result + f;\r
+ int s = (int)(second ^ (second >>> 32));\r
+ result = 31 * result + s;\r
+ return result + index;\r
+ }\r
+ }\r
+ File[] files = folder.toFile().listFiles(new FilenameFilter() {\r
+ @Override\r
+ public boolean accept(File dir, String name) {\r
+ return name.startsWith(ProCoreServer.VALUE_PREFIX);\r
+ }\r
+ });\r
+ HashMap<Value, Value> values = new HashMap<Value, Value>();\r
+ for (int i = 0; i < files.length; ++i) {\r
+ String s = files[i].getName();\r
+ String[] ss = s.split("\\.");\r
+ if (ss.length != 6) {\r
+ Logger.defaultLogError("Illegal external value file name. name=" + s);\r
+ continue;\r
+ }\r
+ long first = convertHexStringToLong(ss[1]);\r
+ long second = convertHexStringToLong(ss[2]);\r
+ int ri = Integer.parseInt(ss[3]);\r
+ long cs = Long.parseLong(ss[4]);\r
+ Value nv = new Value(first, second, ri, cs);\r
+ Value ov = values.get(nv);\r
+ if (null == ov)\r
+ values.put(nv, nv);\r
+ else if (ov.cs < nv.cs) {\r
+ deleteFile(folder, ov.getName(), FileOption.IF_EXIST);\r
+ ov.cs = nv.cs;;\r
+ } else\r
+ deleteFile(folder, nv.getName(), FileOption.IF_EXIST);\r
+ }\r
+ }\r
+ private long getNextClusterId(final Path db) throws ProCoreException {\r
+ long clusterId = 0;\r
+ Path dbHead = db.resolve(ProCoreServer.BRANCH_DIR);\r
+ File[] files = dbHead.toFile().listFiles(new FilenameFilter() {\r
+ @Override\r
+ public boolean accept(File dir, String name) {\r
+ return name.startsWith(ProCoreServer.DATA_PREFIX);\r
+ }\r
+ });\r
+ for (int i = 0; i < files.length; ++i) {\r
+ String s = files[i].getName();\r
+ String[] ss = s.split("\\.", 4);\r
+ if (ss.length != 4) {\r
+ Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
+ continue;\r
+ }\r
+ long id = convertHexStringToLong(ss[2]);\r
+ if (id > clusterId)\r
+ clusterId = id;\r
+ }\r
+ final Path dbTail = db.resolve(ProCoreServer.TAIL_DIR);\r
+ if (!Files.exists(dbTail))\r
+ return clusterId + 1;\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private final PathMatcher matcher;\r
+ long clusterId;\r
+ Visitor(String pattern, long clusterId) {\r
+ this.clusterId = clusterId;\r
+ matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ Path name = file.getFileName();\r
+ if (null == name)\r
+ return FileVisitResult.CONTINUE;\r
+ else if (!matcher.matches(name))\r
+ return FileVisitResult.CONTINUE;\r
+ String s = name.toString();\r
+ String[] ss = s.split("\\.", 4);\r
+ if (ss.length != 4) {\r
+ Logger.defaultLogError("Illegal cluster file name. name=" + s);\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ long id = convertHexStringToLong(ss[2]);\r
+ if (id > clusterId)\r
+ clusterId = id;\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ if (dir.equals(dbTail))\r
+ return FileVisitResult.CONTINUE;\r
+ else\r
+ return FileVisitResult.SKIP_SUBTREE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(ProCoreServer.DATA_PATTERN, clusterId);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(dbTail, opts, Integer.MAX_VALUE, v);\r
+ return v.clusterId + 1;\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not get next free cluster id for " + db, e);\r
+ }\r
+ }\r
+ private static void deleteFiles(final Path folder, String pattern) throws ProCoreException {\r
+ if (!Files.exists(folder))\r
+ return;\r
+ class Visitor extends SimpleFileVisitor<Path> {\r
+ private final PathMatcher matcher;\r
+ Visitor(String pattern) {\r
+ matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);\r
+ }\r
+ @Override\r
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {\r
+ Path name = file.getFileName();\r
+ if (null == name)\r
+ return FileVisitResult.CONTINUE;\r
+ else if (!matcher.matches(name))\r
+ return FileVisitResult.CONTINUE;\r
+ Files.delete(file);\r
+ return FileVisitResult.CONTINUE;\r
+ }\r
+ @Override\r
+ public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {\r
+ if (dir.equals(folder))\r
+ return FileVisitResult.CONTINUE;\r
+ else\r
+ return FileVisitResult.SKIP_SUBTREE;\r
+ }\r
+ }\r
+ try {\r
+ Visitor v = new Visitor(pattern);\r
+ EnumSet<FileVisitOption> opts = EnumSet.noneOf(FileVisitOption.class);\r
+ Files.walkFileTree(folder, opts, Integer.MAX_VALUE, v);\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Could not delete " + folder + " with " + pattern, e);\r
+ }\r
+ }\r
+ public void copy(File to) throws ProCoreException {\r
+ copyDatabaseFiles(server.getFolder().toPath(), to.toPath());\r
+ }\r
+ public boolean isServerAlive() {\r
+ return server.isAlive();\r
+ }\r
+ Client newClient() throws ProCoreException {\r
+ return server.newClient();\r
+ }\r
+ public void createFolder() throws ProCoreException {\r
+ if (server.folder.exists())\r
+ return;\r
+ boolean created = server.folder.mkdirs();\r
+ if (!created)\r
+ throw new ProCoreException("Could not create folder=" + server.folder);\r
+ }\r
+ /**\r
+ * @throws ProCoreException if could not stop server.\r
+ */\r
+ public boolean serverTryToStop() throws ProCoreException {\r
+ return server.tryToStop();\r
+ }\r
+ class Server {\r
+ class Command {\r
+ void Do(ProCoreServer proCoreServer) {\r
+ }\r
+ }\r
+ class Event {\r
+ }\r
+ private final File folder; // Always non null.\r
+ private final ProCoreServer proCoreServer;\r
+ void start() throws ProCoreException {\r
+ try {\r
+ proCoreServer.start();\r
+ } catch (DatabaseLastExitException e) {\r
+ if (null == dbUserAgent)\r
+ return;\r
+ if (!dbUserAgent.handleStart(e))\r
+ throw new ProCoreException(folder, "Failed to handle start exception.", e);\r
+ proCoreServer.start();\r
+ }\r
+ }\r
+ void stop() throws ProCoreException {\r
+ try {\r
+ proCoreServer.stop();\r
+ } catch (InterruptedException e) {\r
+ if (proCoreServer.isAlive())\r
+ throw new ProCoreException("ProCoreServer stop was interrupted.", e);\r
+ }\r
+ }\r
+ boolean isAlive() {\r
+ try {\r
+ return proCoreServer.isAlive();\r
+ } catch (ProCoreException e) {\r
+ Logger.defaultLogError(e);\r
+ return false;\r
+ }\r
+ }\r
+ Server(File aFolder) {\r
+ if (null == aFolder)\r
+ throw new RuntimeException("Database folder can not be null.");\r
+ try {\r
+ folder = aFolder.getCanonicalFile();\r
+ } catch (IOException e) {\r
+ String t = "Could not get canonical path. file=" + aFolder;\r
+ Logger.defaultLogError(t);\r
+ throw new RuntimeException(t);\r
+ }\r
+ try {\r
+ File serverFolder = org.simantics.db.server.internal.Activator.getServerFolder();\r
+ if (!folder.isDirectory())\r
+ folder.mkdirs();\r
+ proCoreServer = ProCoreServer.getProCoreServer(serverFolder, folder);\r
+ return;\r
+ } catch (Throwable t) {\r
+ Logger.defaultLogError(t);\r
+ throw new RuntimeException(t);\r
+ }\r
+ }\r
+ void createConfiguration() throws ProCoreException {\r
+ if (!folder.isDirectory())\r
+ throw new ProCoreException("Can't create configuration because folder is not ok. folder=" + folder.getAbsolutePath());\r
+ File file = new File(folder, ProCoreServer.CONFIG_FILE);\r
+ try {\r
+ file.createNewFile();\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Can't create configuration file. file=" + file.getAbsolutePath());\r
+ }\r
+ }\r
+ void deleteGuard() throws ProCoreException {\r
+ if (server.isActive())\r
+ throw new ProCoreException("Will not delete guard file when server is alive.");\r
+ if (!server.isFolderOk(server.getFolder()))\r
+ throw new ProCoreException("Will not delete guard file when server folder is not ok. folder=" + server.getFolder());\r
+ File file = new File(folder, ProCoreServer.GUARD_FILE);\r
+ if (!file.exists())\r
+ throw new ProCoreException("Guard file does not exist. file=" + file.getAbsolutePath());\r
+ boolean deleted = file.delete();\r
+ if (!deleted)\r
+ throw new ProCoreException("Failed to delete file=" + file.getAbsolutePath());\r
+ }\r
+ String execute(String aCommand) throws ProCoreException {\r
+ try {\r
+ return proCoreServer.execute(aCommand);\r
+ } catch (InterruptedException e) {\r
+ throw new ProCoreException("Execute call was interrupted.", e);\r
+ }\r
+ }\r
+ Status getStatus() {\r
+ Status status = Status.NoDatabase;\r
+ String path;\r
+ try {\r
+ path = folder.getCanonicalPath();\r
+ } catch (IOException e) {\r
+ Util.logError("Could not get canonical path for folder. folder=" + folder.getAbsolutePath(), e);\r
+ path = "<no path>";\r
+ }\r
+ if (!isFolderOk(folder))\r
+ status = Status.NoDatabase;\r
+ else if (!isAlive())\r
+ status = Status.NotRunning;\r
+ else if (isConnected()) {\r
+ if (isLocal())\r
+ status = Status.Local;\r
+ else\r
+ status = Status.Remote;\r
+ } else\r
+ status = Status.Standalone;\r
+ status.message = status.getString() + "@" + path;\r
+ return status;\r
+ }\r
+ File getFolder() {\r
+ return folder;\r
+ }\r
+ boolean isActive() {\r
+ try {\r
+ return proCoreServer.isActive();\r
+ } catch (ProCoreException e) {\r
+ Logger.defaultLogError("IsActive failed.", e);\r
+ return false;\r
+ }\r
+ }\r
+ boolean isConnected() {\r
+ try {\r
+ return proCoreServer.isConnected();\r
+ } catch (ProCoreException e) {\r
+ Logger.defaultLogError("IsConnected failed.", e);\r
+ return false;\r
+ }\r
+ }\r
+ boolean isLocal() {\r
+ try {\r
+ return proCoreServer.isLocal();\r
+ } catch (ProCoreException e) {\r
+ Logger.defaultLogError("IsLocal faailed.", e);\r
+ return false;\r
+ }\r
+ }\r
+ void connect() throws ProCoreException, InterruptedException {\r
+ proCoreServer.connect();\r
+ }\r
+ void disconnect() {\r
+ try {\r
+ proCoreServer.disconnect();\r
+ } catch (ProCoreException e) {\r
+ Logger.defaultLogError("Could not disconnect.", e);\r
+ }\r
+ }\r
+ /**\r
+ * @param aFolder\r
+ * @return true if given folder contains database journal file or\r
+ * configuration file.\r
+ */\r
+ boolean isFolderOk(File aFolder) {\r
+ if (!aFolder.isDirectory())\r
+ return false;\r
+ File config = new File(aFolder, ProCoreServer.CONFIG_FILE);\r
+ if (config.exists())\r
+ return true;\r
+ File journal = new File(aFolder, ProCoreServer.JOURNAL_FILE);\r
+ if (journal.exists())\r
+ return true;\r
+ return false;\r
+ }\r
+ /**\r
+ * @throws ProCoreException if could not stop server.\r
+ */\r
+ boolean tryToStop() throws ProCoreException {\r
+ return proCoreServer.tryToStop();\r
+ }\r
+ Client newClient() throws ProCoreException {\r
+ return proCoreServer.newClient();\r
+ }\r
+ }\r
+// From interface Database\r
+ @Override\r
+ public void initFolder(Properties properties) throws ProCoreException {\r
+ createFolder();\r
+ serverCreateConfiguration();\r
+ }\r
+ @Override\r
+ public void deleteFiles() throws ProCoreException {\r
+ deleteDatabaseFiles();\r
+ }\r
+ @Override\r
+ public void start() throws ProCoreException {\r
+ connect();\r
+ }\r
+ @Override\r
+ public boolean isRunning() throws ProCoreException {\r
+ return isServerAlive();\r
+ }\r
+ @Override\r
+ public boolean tryToStop() throws ProCoreException {\r
+ return serverTryToStop();\r
+ }\r
+ @Override\r
+ public String execute(String aCommand) throws ProCoreException {\r
+ return server.execute(aCommand);\r
+ }\r
+ @Override\r
+ public void purgeDatabase() throws SDBException {\r
+ synchronized (server.proCoreServer.getProCoreClient()) {\r
+ if (!server.isLocal())\r
+ throw new ProCoreException("Purge is allowed only for local server.");\r
+ List<Client> clients = sessionManager.disconnect(this);\r
+ purge();\r
+ try {\r
+ sessionManager.connect(this, clients);\r
+ } catch (InterruptedException e) {\r
+ throw new ProCoreException("Failed to connect after purge.", e);\r
+ }\r
+ }\r
+ }\r
+ @Override\r
+ public Session newSession(ServiceLocator locator) throws ProCoreException {\r
+ return sessionManager.newSession(this);\r
+ }\r
+ @Override\r
+ public Path createFromChangeSets(int revision) throws ProCoreException {\r
+ if (!isFolderOk())\r
+ throw new ProCoreException("Folder must be valid database folder to create database from journal." );\r
+ File folder = server.getFolder();\r
+ File file = new File(folder, ProCoreServer.DCS_FILE);\r
+ if (!file.isFile() && !file.canRead())\r
+ throw new ProCoreException("Dump file must be readable. file=" + file.getAbsolutePath());\r
+ Path db = Paths.get(folder.getAbsolutePath());\r
+ Path temp = createTempFolder(db, "Could not create temporary directory for database to be created from change sets.");\r
+ Server s = new Server(temp.toFile());\r
+ s.createConfiguration();\r
+ s.start();\r
+ String t = s.execute("loadChangeSets .. " + revision);\r
+ if (t.length() < 1)\r
+ throw new ProCoreException("Could not read journal. reply=" + t);\r
+ s.tryToStop();\r
+ try {\r
+ int cs = Integer.parseInt(t);\r
+ if (cs == revision)\r
+ return temp;\r
+ throw new ProCoreException("Could not load change sets. reply=" + t);\r
+ } catch (NumberFormatException e) {\r
+ throw new ProCoreException("Could not load change sets. reply=" + t);\r
+ }\r
+ }\r
+ @Override\r
+ public void clone(File to, int revision, boolean saveHistory) {\r
+ String history = saveHistory ? "with history." : "without history.";\r
+ String message = "Clone to " + to.getAbsolutePath() + "@" + revision + " " + history;\r
+ Util.trace(message);\r
+ }\r
+ @Override\r
+ public DatabaseUserAgent getUserAgent() {\r
+ return dbUserAgent;\r
+ }\r
+ @Override\r
+ public void setUserAgent(DatabaseUserAgent dbUserAgent) {\r
+ this.dbUserAgent = dbUserAgent;\r
+ }\r
+ @Override\r
+ public Status getStatus() {\r
+ return server.getStatus();\r
+ }\r
+ @Override\r
+ public File getFolder() {\r
+ return server.getFolder();\r
+ }\r
+ @Override\r
+ public boolean isFolderOk() {\r
+ return server.isFolderOk(server.getFolder());\r
+ }\r
+ @Override\r
+ public boolean isFolderOk(File folder) {\r
+ return server.isFolderOk(folder);\r
+ }\r
+ public boolean isFolderEmpty() {\r
+ return isFolderEmpty(server.getFolder());\r
+ }\r
+ @Override\r
+ public boolean isFolderEmpty(File folder) {\r
+ return isFolderEmpty(folder.toPath());\r
+ }\r
+ @Override\r
+ public void deleteGuard() throws ProCoreException {\r
+ server.deleteGuard();\r
+ }\r
+ @Override\r
+ public boolean isConnected() throws ProCoreException {\r
+ return server.isConnected();\r
+ }\r
+ @Override\r
+ public void connect() throws ProCoreException {\r
+ if (!isFolderOk())\r
+ throw new ProCoreException("Could not connect to " + getFolder());\r
+ if (!server.isAlive())\r
+ server.start();\r
+ if (isConnected())\r
+ return;\r
+ try {\r
+ server.connect();\r
+ } catch (InterruptedException e) {\r
+ Util.logError("Server connect was interrupted.", e);\r
+ }\r
+ if (server.isActive())\r
+ return;\r
+ throw new ProCoreException("Could not connect to " + getFolder());\r
+ }\r
+ @Override\r
+ public void disconnect() throws ProCoreException {\r
+ server.disconnect();\r
+ }\r
+ @Override\r
+ public Path dumpChangeSets() throws ProCoreException {\r
+ if (!isFolderOk())\r
+ throw new ProCoreException("Folder must be set to dump change sets.");\r
+ if (!server.isActive())\r
+ throw new ProCoreException("Server must be responsive to dump change sets.");\r
+ String t = server.execute("dumpChangeSets").replaceAll("\n", "");\r
+ try {\r
+ int ncs = Integer.parseInt(t);\r
+ if (ncs < 1)\r
+ return null;\r
+ File file = new File(getFolder(), ProCoreServer.DCS_FILE);\r
+ return file.toPath();\r
+ } catch (NumberFormatException e) {\r
+ throw new ProCoreException("Could not dump change sets.", e);\r
+ }\r
+ }\r
+ @Override\r
+ public long serverGetTailChangeSetId() throws ProCoreException {\r
+ try {\r
+ return server.proCoreServer.getTailData().nextChangeSetId;\r
+ } catch (TailReadException e) {\r
+ return 1;\r
+ }\r
+ }\r
+ @Override\r
+ public JournalI getJournal() throws ProCoreException {\r
+ return journal;\r
+ }\r
+ class JournalI implements Database.Journal {\r
+ class Analyzed {\r
+ private final File journalFile;\r
+ private long lastModified;\r
+ private RandomAccessFile file; // Journal file.\r
+ private ArrayList<Long> offsets = new ArrayList<Long>(); // Offsets to commands in journal file.\r
+ private Line lines[] = new Line[256];\r
+ int firstLine = 0; // Index of the first element in lines table.\r
+ Analyzed(File dbFolder) {\r
+ journalFile = new File(dbFolder, ProCoreServer.JOURNAL_FILE);\r
+ for (int i=0; i<lines.length; ++i)\r
+ lines[i] = new Line();\r
+ }\r
+ private boolean canRead() {\r
+ return journalFile.isFile() && journalFile.canRead();\r
+ }\r
+ private int count() {\r
+ try {\r
+ return readOffsets().size();\r
+ } catch (ProCoreException e) {\r
+ Util.logError("Failed to read or analyze journal. file=" + journalFile, e);\r
+ clear();\r
+ return 0;\r
+ }\r
+ }\r
+ private void clear() {\r
+ close();\r
+ offsets.clear();\r
+ }\r
+ private void close() {\r
+ if (file != null) {\r
+ try {\r
+ file.close();\r
+ } catch (Throwable e) {\r
+ Logger.defaultLogError("Close file threw an exception.", e);\r
+ }\r
+ file = null;\r
+ }\r
+ }\r
+ private void open() throws ProCoreException {\r
+ try {\r
+ file = new RandomAccessFile(journalFile, "r");\r
+ } catch (Throwable e) {\r
+ file = null;\r
+ throw new ProCoreException("Failed to open journal file.", e);\r
+ }\r
+ }\r
+ private String getComment(byte[] bytes) throws ProCoreException {\r
+ Serializer METADATA_SERIALIZER = Bindings.getSerializerUnchecked(new TreeMapBinding(Bindings.STRING, Bindings.BYTE_ARRAY));\r
+ try {\r
+ @SuppressWarnings("unchecked")\r
+ TreeMap<String, byte[]> metadata = (TreeMap<String, byte[]>) METADATA_SERIALIZER.deserialize(bytes);\r
+ CommitMetadata commit = MetadataUtil.getMetadata(metadata, CommitMetadata.class);\r
+ String date = "<metadata does not contain date>";\r
+ if (null != commit && null != commit.date)\r
+ date = commit.date.toString();\r
+ CommentMetadata comment = MetadataUtil.getMetadata(metadata, CommentMetadata.class);\r
+ String comments = "<metadata does not contain comment>";\r
+ if (null != comment)\r
+ comments = comment.toString();\r
+ return date + " " + comments;\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Failed to interpret metadata.", e);\r
+ }\r
+ }\r
+ private Line getRow(int index, Line line) throws ProCoreException {\r
+ if (index < 0 || index >= offsets.size())\r
+ return null; // Index out of range.\r
+ if (index < firstLine || index >= firstLine + lines.length) {\r
+ readLines(index);\r
+ if (index < firstLine)\r
+ return null; // Index out of range.\r
+ }\r
+ int offset = index - firstLine;\r
+ line.status = lines[offset].status;\r
+ line.request = lines[offset].request;\r
+ line.comment = lines[offset].comment;\r
+ return line;\r
+ }\r
+ private void readLines(int first) throws ProCoreException {\r
+ open();\r
+ try {\r
+ for (int i=0, index = first; i<lines.length; ++i, ++index)\r
+ readLine(index, lines[i]);\r
+ firstLine = first;\r
+ } finally {\r
+ close();\r
+ }\r
+ }\r
+ private void readLine(int index, Line line) throws ProCoreException {\r
+ if (index >= offsets.size()) {\r
+ line.status = false;\r
+ line.request = "<Illegal request.>";\r
+ line.comment = "<Illegal request.>";\r
+ return;\r
+ }\r
+ long offset = offsets.get(index);\r
+ try {\r
+ file.seek(offset);\r
+ int i = file.readInt();\r
+ int length = Integer.reverseBytes(i);\r
+ byte b = file.readByte();\r
+ boolean ok = b != 0;\r
+ b = file.readByte();\r
+ // boolean littleEndian = b != 0;\r
+ i = file.readInt();\r
+ int type = Integer.reverseBytes(i);\r
+ String comment = "";\r
+ if (length < 6)\r
+ throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+ else if (length > 6) {\r
+ if (type != 3)\r
+ file.skipBytes(length - 6);\r
+ else if (length > 22){\r
+ file.skipBytes(16);\r
+ i = file.readInt();\r
+ int size = Integer.reverseBytes(i);\r
+ if (size != length - 26)\r
+ throw new ProCoreException("Metadata corrupted at" + file.getFilePointer() + ".");\r
+ byte[] bytes = new byte[size];\r
+ file.readFully(bytes);\r
+ comment = getComment(bytes);\r
+ if (null == comment)\r
+ comment = "<metadata does not contain comment>";\r
+ }\r
+ }\r
+ i = file.readInt();\r
+ int length2 = Integer.reverseBytes(i);\r
+ if (length != length2)\r
+ throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+ String command = MessageText.get(type);\r
+ line.status = ok;\r
+ line.request = command;\r
+ line.comment = comment;\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Journal file corrupted.");\r
+ }\r
+ }\r
+ private ArrayList<Long> readOffsets() throws ProCoreException {\r
+ if (!canRead()) {\r
+ lastModified = 0;\r
+ offsets.clear();\r
+ firstLine = 0;\r
+ return offsets;\r
+ }\r
+ long modified = journalFile.lastModified();\r
+ if (lastModified != 0 && lastModified == modified)\r
+ return offsets; // Offsets already up to date.\r
+ lastModified = 0;\r
+ offsets.clear();\r
+ firstLine = 0;\r
+ open();\r
+ try {\r
+ file.seek(0);\r
+ int i = file.readInt();\r
+ int version = Integer.reverseBytes(i);\r
+ if (version != 1)\r
+ throw new ProCoreException("Unsupported journal file version. expected=1 got=" + version);\r
+ i = file.readInt();\r
+ int major = Integer.reverseBytes(i);\r
+ if (major != MessageNumber.ProtocolVersionMajor)\r
+ throw new ProCoreException("Unsupported journal request major version. expected=" + MessageNumber.ProtocolVersionMajor + " got=" + major);\r
+ i = file.readInt();\r
+ int minor = Integer.reverseBytes(i);\r
+ if (minor > MessageNumber.ProtocolVersionMinor)\r
+ throw new ProCoreException("Unsupported journal request minor version. expected=" + MessageNumber.ProtocolVersionMinor + " got=" + minor);\r
+ i = file.readInt();\r
+ int length = Integer.reverseBytes(i);\r
+ while (length > 0) { // Not supporting unsigned integers.\r
+ if (length < 6)\r
+ throw new ProCoreException("Journal file corrupted at" + file.getFilePointer() + ".");\r
+ file.skipBytes(length);\r
+ i = file.readInt();\r
+ int length2 = Integer.reverseBytes(i);\r
+ if (length != length2)\r
+ throw new ProCoreException("Journal file corrupted at " + file.getFilePointer() + ".");\r
+ long offset = file.getFilePointer() - length - 8;\r
+ offsets.add(offset);\r
+ i = file.readInt();\r
+ length = Integer.reverseBytes(i);\r
+ }\r
+ } catch (EOFException e) {\r
+ } catch (IOException e) {\r
+ throw new ProCoreException("Failed to get command count.", e);\r
+ } finally {\r
+ close();\r
+ }\r
+ lastModified = modified;\r
+ readLines(0);\r
+ return offsets;\r
+ }\r
+ }\r
+ private final Analyzed analyzed;\r
+ JournalI(File dbFolder) {\r
+ this.analyzed = new Analyzed(dbFolder);\r
+ }\r
+ @Override\r
+ public boolean canRead() {\r
+ return analyzed.canRead() && !isServerAlive();\r
+ }\r
+ @Override\r
+ public int count() {\r
+ return analyzed.count();\r
+ }\r
+ @Override\r
+ public int read(int index, Line line) throws ProCoreException {\r
+ int count = analyzed.count();\r
+ analyzed.getRow(index, line);\r
+ return count;\r
+ }\r
+ }\r
+ @Override\r
+ public String getCompression() {\r
+ return "FLZ";\r
+ }\r
+}\r