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