]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics/src/org/simantics/SimanticsPlatform.java
Support using a DB baseline for faster program startup.
[simantics/platform.git] / bundles / org.simantics / src / org / simantics / SimanticsPlatform.java
index 09d632a3edce942d288a44d8f1b8a19d51cda9c6..961dfc71899d8a872d8941dda396f95a8ab2f0ca 100644 (file)
@@ -19,6 +19,12 @@ import static org.simantics.db.common.utils.Transaction.writeGraph;
 
 import java.io.File;
 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.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
@@ -29,6 +35,8 @@ import java.util.Properties;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
 
 import org.eclipse.core.runtime.ILog;
 import org.eclipse.core.runtime.IProduct;
@@ -39,6 +47,10 @@ import org.eclipse.core.runtime.Platform;
 import org.eclipse.core.runtime.Status;
 import org.eclipse.core.runtime.SubMonitor;
 import org.eclipse.osgi.service.resolver.BundleDescription;
+import org.ini4j.Ini;
+import org.ini4j.InvalidFileFormatException;
+import org.simantics.SimanticsPlatform.OntologyRecoveryPolicy;
+import org.simantics.SimanticsPlatform.RecoveryPolicy;
 import org.simantics.databoard.Bindings;
 import org.simantics.databoard.Databoard;
 import org.simantics.datatypes.literal.Font;
@@ -79,6 +91,7 @@ import org.simantics.graph.db.GraphDependencyAnalyzer;
 import org.simantics.graph.db.GraphDependencyAnalyzer.IU;
 import org.simantics.graph.db.GraphDependencyAnalyzer.IdentityNode;
 import org.simantics.graph.db.IImportAdvisor;
+import org.simantics.graph.db.ImportResult;
 import org.simantics.graph.db.TransferableGraphs;
 import org.simantics.graph.diff.Diff;
 import org.simantics.graph.diff.TransferableGraphDelta1;
@@ -103,6 +116,7 @@ import org.simantics.project.management.WorkspaceUtil;
 import org.simantics.utils.FileUtils;
 import org.simantics.utils.datastructures.Pair;
 import org.simantics.utils.logging.TimeLogger;
+import org.simantics.utils.strings.EString;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -155,6 +169,9 @@ public class SimanticsPlatform implements LifecycleListener {
     /** Set to true when the Simantics Platform is in good-and-go condition */
     public boolean running;
 
+    /** ID of the database driver that the platform is currently using */
+    private String currentDatabaseDriver;
+
     /** Database Session */
     public Session session;
     private Management databasebManagement;
@@ -204,24 +221,31 @@ public class SimanticsPlatform implements LifecycleListener {
     private Session setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
         if (progressMonitor == null)
             progressMonitor = new NullProgressMonitor();
-        File dbLocation = Platform.getLocation().append("db").toFile();
+        Path workspaceLocation = Platform.getLocation().toFile().toPath();
+        Path dbLocation = workspaceLocation.resolve("db");
+        Path dbIniPath = workspaceLocation.resolve("db.ini");
+        // The driver file overrides any command line arguments to prevent
+        // using the wrong driver for an existing database directory.
         ServerManager serverManager;
         try {
-            serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.getAbsolutePath());
+            Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
+            databaseDriverId = dbIni.get("driver", "id");
+            serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
         } catch (DatabaseException | IOException e) {
-            throw new PlatformException("Failed to initialize Server Manager", e);
+            throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
         }
         progressMonitor.beginTask("Setting up Simantics Database", 100);
         progressMonitor.setTaskName("Asserting Database is installed.");
         String msg = "Failed to initialize Simantics database.";
         try {
             // Create database
-            log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Creating database at " + dbLocation));
+            log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
             progressMonitor.setTaskName("Creating database at " + dbLocation);
-            databasebManagement = serverManager.getManagement(dbLocation);
+            databasebManagement = serverManager.getManagement(dbLocation.toFile());
             databasebManagement.create();
+            currentDatabaseDriver = databaseDriverId;
             // Create layer0.
-            return serverManager.createDatabase(dbLocation);
+            return serverManager.createDatabase(dbLocation.toFile());
         } catch (DatabaseException e) {
             throw new PlatformException(msg, e);
         } catch (Throwable e) {
@@ -387,15 +411,22 @@ public class SimanticsPlatform implements LifecycleListener {
                                final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
                                final GraphBundle oldTG = reinstallTGs.get(tg);
 
+                               boolean createImmutable = tg.getImmutable();
+
                                if (oldTG==null) {
-                                       
-                                       boolean createImmutable = tg.getImmutable();
+
                                 session.getService(XSupport.class).setServiceMode(true, createImmutable);
 
                                        // Install TG
                                        log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
-                                       TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
+                                       ImportResult result = TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
+                                       if (!result.missingExternals.isEmpty()) {
+                                               log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Import of " + tg.toString() + " was missing the following external entities:\n" + EString.implode(result.missingExternals)));
+                                       }
                                } else {
+                                       if(!createImmutable)
+                                               continue;
+
                                        // Merge TG
                                        startTransaction(session, false);
                                        TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
@@ -682,6 +713,75 @@ public class SimanticsPlatform implements LifecycleListener {
         resetDatabase(monitor);
     }
 
+    public boolean handleBaselineDatabase() 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;
+        }
+
+        String dbBaselineArchive = System.getProperty("org.simantics.db.baseline", null);
+        if (dbBaselineArchive == null)
+            return false;
+
+        Path baseline = Paths.get(dbBaselineArchive);
+        if (!Files.isRegularFile(baseline))
+            throw new PlatformException("Specified database baseline archive " + baseline + " does not exist. Cannot initialize workspace database.");
+
+        validateBaselineFile(baseline);
+        validateWorkspaceForBaselineInitialization(workspaceLocation);
+
+        try {
+            Files.createDirectories(workspaceLocation);
+            FileUtils.extractZip(baseline.toFile(), workspaceLocation.toFile());
+            Files.write(baselineIndicatorFile, baselineIndicatorContents(baselineIndicatorFile));
+            return true;
+        } catch (IOException e) {
+            throw new PlatformException(e);
+        }
+    }
+
+    private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("d. MMM yyyy HH:mm:ss");
+
+    private 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");
+    }
+
+    private void validateWorkspaceForBaselineInitialization(Path workspaceLocation) throws PlatformException {
+        try {
+            Path db = workspaceLocation.resolve("db");
+            if (Files.exists(db))
+                throw new PlatformException("Database location " + db + " already exists. Cannot re-initialize workspace from baseline.");
+            Path index = workspaceLocation.resolve(".metadata/.plugins/org.simantics.db.indexing");
+            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;
+    }
+
+    private void validateBaselineFile(Path baseline) throws PlatformException {
+        try (ZipFile zip = new ZipFile(baseline.toFile())) {
+            ZipEntry db = zip.getEntry("db");
+            if (db == null)
+               throw new PlatformException("Baseline archive does not contain database directory 'db'");
+            ZipEntry index = zip.getEntry(".metadata/.plugins/org.simantics.db.indexing");
+            if (index == null)
+                throw new PlatformException("Baseline archive does not contain database index directory '.metadata/.plugins/org.simantics.db.indexing'");
+        } catch (IOException e) {
+            throw new PlatformException("Failed to validate baseline archive " + baseline, e);
+        }
+    }
+
     /**
      * Start-up the platform. The procedure consists of 8 steps. Once everything
      * is up and running, all fields are set property.
@@ -734,13 +834,28 @@ public class SimanticsPlatform implements LifecycleListener {
         // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
         VariableRepository.clear();
         
+        // 0.3 Handle baseline database before opening db
+        boolean usingBaseline = handleBaselineDatabase();
+        
         // 1. Assert there is a database at <workspace>/db
         session = setupDatabase(databaseDriverId, progressMonitor, workspacePolicy, userAgent);
         TimeLogger.log("Database setup complete");
-
+        
+        // 1.1 
+        XSupport support = session.getService(XSupport.class);
+        if (support.rolledback()) {
+            try {
+                DatabaseIndexing.deleteAllIndexes();
+            } catch (IOException e) {
+                throw new PlatformException(e);
+            }
+        }
+        
         // 2. Assert all graphs, and correct versions, are installed to the database
-        synchronizeOntologies(progressMonitor, ontologyPolicy, requireSynchronize);
-        TimeLogger.log("Synchronized ontologies");
+        if(!usingBaseline) {
+            synchronizeOntologies(progressMonitor, ontologyPolicy, requireSynchronize);
+            TimeLogger.log("Synchronized ontologies");
+        }
 
         // 4. Assert simantics.cfg exists
         boolean installProject = assertConfiguration(progressMonitor,workspacePolicy);
@@ -829,8 +944,11 @@ public class SimanticsPlatform implements LifecycleListener {
         try {
             // Construct and initialize SessionContext from Session.
             SessionContext sessionContext = SessionContext.create(session, init);
-            if (init)
+            TimeLogger.log("Session context created");
+            if (init) {
                 sessionContext.registerServices();
+                TimeLogger.log("Session services registered");
+            }
             return sessionContext;
         } catch (DatabaseException e) {
             throw new PlatformException(e);
@@ -960,6 +1078,7 @@ public class SimanticsPlatform implements LifecycleListener {
 
             session = null;
             projectResource = null;
+            currentDatabaseDriver = null;
 
             DependenciesRelation.assertFinishedTracking();
 
@@ -1022,6 +1141,8 @@ public class SimanticsPlatform implements LifecycleListener {
 
     public void reconnect(String databaseDriverId) throws Exception {
         // Starts database server.
+        if (currentDatabaseDriver != null)
+            databaseDriverId = currentDatabaseDriver;
         SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
     }
 
@@ -1033,5 +1154,17 @@ public class SimanticsPlatform implements LifecycleListener {
         }
     }
 
-}
+    private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
+            throws InvalidFileFormatException, IOException
+    {
+        File f = path.toFile();
+        Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
+        String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
+        if (iniId == null) {
+            dbIni.put("driver", "id", databaseDriverId);
+            dbIni.store(f);
+        }
+        return dbIni;
+    }
 
+}