package org.simantics; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.time.Instant; import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; import java.util.zip.ZipOutputStream; import org.eclipse.core.runtime.Platform; import org.simantics.utils.FileUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * @author Tuukka Lehtonen * @since 1.34.0 */ public class DatabaseBaselines { private static final Logger LOGGER = LoggerFactory.getLogger(DatabaseBaselines.class); private static final boolean REQUIRE_INDEX_IN_BASELINE = false; private static final String DB_DIRECTORY = "db"; //$NON-NLS-1$ private static final String INDEX_DIRECTORY = ".metadata/.plugins/org.simantics.db.indexing"; //$NON-NLS-1$ private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("d. MMM yyyy HH:mm:ss"); public static boolean handleBaselineDatabase(Path installLocation, boolean databaseExists) throws PlatformException { Path workspaceLocation = Platform.getLocation().toFile().toPath(); Path baselineIndicatorFile = workspaceLocation.resolve(".baselined"); if (Files.isRegularFile(baselineIndicatorFile)) { // This means that the workspace has already been initialized from // a database baseline and further initialization is not necessary. return true; } Path baseline = resolveBaselineFile(installLocation); if (baseline == null) { baseline = getAutomaticBaselinePath(); if(baseline == null) return false; if(databaseExists) return false; if(!existsAutomaticBaseline()) return false; } DatabaseBaselines.validateBaselineFile(baseline); DatabaseBaselines.validateWorkspaceForBaselineInitialization(workspaceLocation); DatabaseBaselines.initializeWorkspaceWithBaseline(baseline, workspaceLocation, baselineIndicatorFile); return true; } private static Path resolveBaselineFile(Path installLocation) throws PlatformException { String dbBaselineArchive = System.getProperty("org.simantics.db.baseline", null); if (dbBaselineArchive == null) return null; Path baseline = Paths.get(dbBaselineArchive); if (baseline.isAbsolute()) { if (!Files.isRegularFile(baseline)) throw new PlatformException("Specified database baseline archive " + baseline + " does not exist. Cannot initialize workspace database from baseline."); return baseline; } // Relative path resolution order: // 1. from the platform "install location" // 2. from working directory //Path installLocation = tryGetInstallLocation(); if (installLocation != null) { Path installedBaseline = installLocation.resolve(dbBaselineArchive); if (Files.isRegularFile(installedBaseline)) return installedBaseline; } if (!Files.isRegularFile(baseline)) throw new PlatformException("Specified database baseline archive " + baseline + " does not exist in either the install location (" + installLocation + ") or the working directory (" + Paths.get(".").toAbsolutePath() + "). Cannot initialize workspace database."); return null; } private static boolean useAutomaticBaseline() { return getAutomaticBaselinePath() != null; } private static Path getAutomaticBaselinePath() { if("true".equals(System.getProperty("org.simantics.db.baseline.automatic"))) return Paths.get("automatic_baseline", "baseline.zip"); else return null; } private static boolean existsAutomaticBaseline() { Path baselineFile = getAutomaticBaselinePath(); if(baselineFile == null) return false; return Files.exists(baselineFile); } public static boolean shouldCreateAutomaticBaseline(boolean existsDatabase) throws PlatformException { if(!useAutomaticBaseline()) { // Are we using this feature? return false; } if(existsDatabase) { // Baseline can only be created after db initialization return false; } if(existsAutomaticBaseline()) { // Existing baselines should not be automatically overridden return false; } return true; } public static void createAutomaticBaseline(Path dbLocation) throws PlatformException { if(existsAutomaticBaseline()) return; try { DatabaseBaselines.packageBaseline(dbLocation.getParent(), getAutomaticBaselinePath()); } catch (IOException e) { LOGGER.error("Error while creating automatic baseline", e); } } public static Path packageBaseline(Path fromWorkspace, Path packageFilePath) throws IOException { Files.createDirectories(packageFilePath.getParent()); return compressZip(fromWorkspace, collectBaselinePaths(fromWorkspace), packageFilePath); } private static List collectBaselinePaths(Path workspace) throws IOException { Path dbPath = workspace.resolve(DB_DIRECTORY); Path indexPath = workspace.resolve(INDEX_DIRECTORY); if (!Files.isDirectory(dbPath)) throw new IllegalArgumentException("workspace database directory " + dbPath + " does not exist"); List paths = Files.walk(dbPath).collect(Collectors.toList()); if (Files.isDirectory(indexPath)) { List indexPaths = Files.walk(indexPath).collect(Collectors.toList()); paths.addAll(indexPaths); } else { if (REQUIRE_INDEX_IN_BASELINE) throw new IllegalArgumentException("workspace database index directory " + indexPath + " does not exist"); } return paths; } private static Path compressZip(Path relativeRoot, List paths, Path zipFile) throws IOException { if (LOGGER.isDebugEnabled()) LOGGER.debug("Compressing " + paths.size() + " path entries into ZIP file " + zipFile); try (ZipOutputStream zout = new ZipOutputStream(Files.newOutputStream(zipFile))) { compressZip(relativeRoot, zout, paths); return zipFile; } finally { if (LOGGER.isDebugEnabled()) LOGGER.debug("Compressed " + paths.size() + " entries into " + zipFile); } } private static void compressZip(Path relativeRoot, ZipOutputStream zout, List paths) throws IOException { for (Path p : paths) { Path rp = relativeRoot.relativize(p); String name = rp.toString(); if (Files.isDirectory(p)) { name = name.endsWith("/") ? name : name + "/"; zout.putNextEntry(new ZipEntry(name)); } else { zout.putNextEntry(new ZipEntry(name)); FileUtils.copy(p.toFile(), zout); zout.closeEntry(); } } } public static byte[] baselineIndicatorContents(Path path) throws IOException { return String.format("%s%n%s%n", path.toString(), Instant.now().atZone(ZoneId.systemDefault()).format(TIMESTAMP_FORMAT)) .getBytes("UTF-8"); } public static void validateWorkspaceForBaselineInitialization(Path workspaceLocation) throws PlatformException { try { Path db = workspaceLocation.resolve(DB_DIRECTORY); if (Files.exists(db)) throw new PlatformException("Database location " + db + " already exists. Cannot re-initialize workspace from baseline."); if (REQUIRE_INDEX_IN_BASELINE) { Path index = workspaceLocation.resolve(INDEX_DIRECTORY); if (Files.exists(index) || !isEmptyDirectory(index)) throw new PlatformException("Index location " + index + " already exists. Cannot re-initialize workspace from baseline."); } } catch (IOException e) { throw new PlatformException("Failed to validate workspace for baseline initialization", e); } } private static boolean isEmptyDirectory(Path dir) throws IOException { return Files.walk(dir).count() == 1; } public static void validateBaselineFile(Path baseline) throws PlatformException { try (ZipFile zip = new ZipFile(baseline.toFile())) { ZipEntry db = zip.getEntry(DB_DIRECTORY); if (db == null) throw new PlatformException("Baseline archive does not contain database directory '" + DB_DIRECTORY + "'"); if (REQUIRE_INDEX_IN_BASELINE) { ZipEntry index = zip.getEntry(INDEX_DIRECTORY); if (index == null) throw new PlatformException("Baseline archive does not contain database index directory '" + INDEX_DIRECTORY + "'"); } } catch (IOException e) { throw new PlatformException("Failed to validate baseline archive " + baseline, e); } } public static void initializeWorkspaceWithBaseline(Path baseline, Path workspaceLocation, Path indicatorPath) throws PlatformException { try { Files.createDirectories(workspaceLocation); FileUtils.extractZip(baseline.toFile(), workspaceLocation.toFile()); if (indicatorPath != null) Files.write(indicatorPath, DatabaseBaselines.baselineIndicatorContents(indicatorPath)); } catch (IOException e) { throw new PlatformException(e); } } public static void main(String[] args) throws IOException { packageBaseline(Paths.get("D:/temp/desktop/workspace"), Paths.get("d:/temp/desktop/workspace/baseline.zip")); } }