]> gerrit.simantics Code Review - simantics/platform.git/blobdiff - bundles/org.simantics/src/org/simantics/SimanticsPlatform.java
SimanticsPlatform.startUp ensures updateness of installed feature groups
[simantics/platform.git] / bundles / org.simantics / src / org / simantics / SimanticsPlatform.java
index cf4928fe793e1088d2c28da8587b9ad538d3f370..54b96054719e993986a7ab6ad2e938b67fc0e430 100644 (file)
@@ -21,6 +21,10 @@ 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;
@@ -31,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;
@@ -73,6 +79,7 @@ import org.simantics.db.layer0.util.TGTransferableGraphSource;
 import org.simantics.db.layer0.variable.VariableRepository;
 import org.simantics.db.management.SessionContext;
 import org.simantics.db.request.Read;
+import org.simantics.db.request.Write;
 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
 import org.simantics.db.service.LifecycleSupport.LifecycleState;
 import org.simantics.db.service.QueryControl;
@@ -83,6 +90,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;
@@ -107,6 +115,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;
 
@@ -184,6 +193,19 @@ public class SimanticsPlatform implements LifecycleListener {
 
     public Thread mainThread;
 
+    private Thread shutdownHook = new Thread() {
+        @Override
+        public void run() {
+            try {
+                LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
+                shutdown(null, false);
+            } catch (PlatformException e) {
+                LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
+                log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
+            }
+        }
+    };
+
     /**
      * The {@link IProject} activated by
      * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
@@ -376,21 +398,18 @@ public class SimanticsPlatform implements LifecycleListener {
                         }
                         throw new PlatformException(sb.toString());
                     }
-                    
+
                     List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
                     if(!sortedBundles.isEmpty()) {
-                       
-                        session.syncRequest(new WriteRequest() {
-                            @Override
-                            public void perform(WriteGraph graph) throws DatabaseException {
-                                try {
-                                    graph.newClusterSet(graph.getRootLibrary());
-                                } catch (ClusterSetExistException e) {
-                                    // Cluster set exist already, no problem.
-                                }
-                                graph.setClusterSet4NewResource(graph.getRootLibrary());
-                                graph.flushCluster();
+
+                        session.syncRequest((Write) graph -> {
+                            try {
+                                graph.newClusterSet(graph.getRootLibrary());
+                            } catch (ClusterSetExistException e) {
+                                // Cluster set exist already, no problem.
                             }
+                            graph.setClusterSet4NewResource(graph.getRootLibrary());
+                            graph.flushCluster();
                         });
 
                         boolean mergedOntologies = false;
@@ -401,15 +420,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();
@@ -441,13 +467,10 @@ public class SimanticsPlatform implements LifecycleListener {
                                        }
                                }
                         }
-                        
-                        session.syncRequest(new WriteRequest() {
-                            @Override
-                            public void perform(WriteGraph graph) throws DatabaseException {
-                                graph.setClusterSet4NewResource(graph.getRootLibrary());
-                                graph.flushCluster();
-                            }
+
+                        session.syncRequest((Write) graph -> {
+                            graph.setClusterSet4NewResource(graph.getRootLibrary());
+                            graph.flushCluster();
                         });
 
                         if (mergedOntologies)
@@ -553,38 +576,11 @@ public class SimanticsPlatform implements LifecycleListener {
             Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
             Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
 
-    //        final List<String> Platform_Features = new ArrayList<String>();
-    //
-    //        // Convert graph instances
-    //        Collection<TransferableGraph1> platformGraphs = new ArrayList<TransferableGraph1>();
-    //        for (GraphBundleEx e : platformTGs.values()) platformGraphs.add( e.getGraph() );
-    //        IGraph graph = Graphs.createGraph(platformGraphs);
-    //
-    //        Res    PublishedProjectFeatures = UriUtils.uriToPath( ProjectResource.URIs.PublishedProjectFeatures );
-    //        Path   HasFeature = UriUtils.uriToPath( ProjectResource.URIs.HasFeature );
-    //        for(Res feature : graph.getObjects(PublishedProjectFeatures, HasFeature)) {
-    //            System.out.println("Installing Project Feature: "+feature.toString());
-    //            Platform_Features.add( feature.toString() );
-    //        }
-
             try {
-                Transaction.startTransaction(session, true);
-                try {
-    //                for (String feature : Platform_Features) {
-    //                    try {
-    //                        getResource(feature);
-    //                    } catch(ResourceNotFoundException e) {
-    //                        System.out.println(feature+" not found");
-    //                    }
-    //                    mgmt.installFeature(projectResource, feature);
-    //                }
-                    Projects.setProjectInstalledGroups(writeGraph(), projectResource, groupsWithoutVersion);
-                    Transaction.commit();
-                } finally {
-                    Transaction.endTransaction();
-                }
-                //session.getService( LifecycleSupport.class ).save();
-            } catch(DatabaseException ae) {
+                session.syncRequest(
+                        (Write) graph ->
+                        Projects.setProjectInstalledGroups(graph, projectResource, groupsWithoutVersion));
+            } catch (DatabaseException ae) {
                 throw new PlatformException("Failed to install features", ae);
             }
             progressMonitor.worked(10);
@@ -696,6 +692,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.
@@ -706,7 +771,8 @@ public class SimanticsPlatform implements LifecycleListener {
      * installing project features.
      * <p>
      *
-     * In SWB this is handled in SimanticsWorkbenchAdvisor#openWindows().
+     * In Simantics Workbench this is handled in
+     * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
      * <p>
      *
      * If remote server is given, simantics plaform takes connection there
@@ -720,7 +786,7 @@ public class SimanticsPlatform implements LifecycleListener {
      *        startup or <code>null</code> to resort to default measures
      * @throws PlatformException
      */
-    public SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
+    public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
             OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
     throws PlatformException
     {
@@ -748,6 +814,9 @@ 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");
@@ -763,8 +832,10 @@ public class SimanticsPlatform implements LifecycleListener {
         }
         
         // 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);
@@ -773,7 +844,7 @@ public class SimanticsPlatform implements LifecycleListener {
         installProject = assertProject(progressMonitor, workspacePolicy, installProject);
 
         // 6. Install all features into project, if in debug mode
-        updateInstalledGroups(progressMonitor, installProject);
+        updateInstalledGroups(progressMonitor, true); //installProject);
         TimeLogger.log("Installed all features into project");
 
         // 7. Assert L0.Session in database for this session
@@ -782,14 +853,9 @@ public class SimanticsPlatform implements LifecycleListener {
         session.getService(XSupport.class).setServiceMode(false, false);
 
         try {
-            session.sync(new WriteRequest() {
-
-                @Override
-                public void perform(WriteGraph graph) throws DatabaseException {
-                    QueryControl qc = graph.getService(QueryControl.class);
-                    qc.flush(graph);
-                }
-
+            session.syncRequest((Write) graph -> {
+                QueryControl qc = graph.getService(QueryControl.class);
+                qc.flush(graph);
             });
             TimeLogger.log("Flushed queries");
         } catch (DatabaseException e) {
@@ -845,6 +911,9 @@ public class SimanticsPlatform implements LifecycleListener {
 
         running = true;
 
+        // #7650: improve shutdown robustness in all applications that use the platform
+        Runtime.getRuntime().addShutdownHook(shutdownHook);
+
         return sessionContext;
 
     }
@@ -853,87 +922,42 @@ 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);
         }
     }
 
-//    private static File getIgnorePrerequisitesFile(URL workspaceUrl) {
-//        if (workspaceUrl == null)
-//            return null;
-//        return new File(workspaceUrl.getPath(), ".ignorePrerequisites");
-//    }
-//
-//    private void ensurePrerequisites(IProgressMonitor progressMonitor, PlatformUserAgent userAgent) throws PlatformException {
-//        Location loc = Platform.getInstanceLocation();
-//        File ignorePrerequisites = getIgnorePrerequisitesFile(loc.getURL());
-//        if (loc.isSet() && ignorePrerequisites != null) {
-//            if (ignorePrerequisites.exists() || ignorePrerequisites.isFile())
-//                return;
-//        }
-//
-//        try {
-//            ServerEnvironment.ensureServerDependenciesMet();
-//        } catch (ExecutionEnvironmentException e) {
-//            // Not installed properly, ask user whether to try installation.
-//            try {
-//                StringBuilder msg = new StringBuilder();
-//                msg.append("Your system seems to be missing the following prerequisites for running this application:\n\n");
-//                for (Product product : e.requiredProducts)
-//                    msg.append("\t" + product.getDescription() + "\n");
-//                msg.append("\nYou can either install the missing components now or ignore and attempt to start the application without them. Ignore Always will ignore this question for this workspace.");
-//                msg.append("\n\nSelecting Cancel will close the application.");
-//
-//                int selection = 0;
-//                if (userAgent != null) {
-//                    selection = userAgent.showPrompt("Missing Prerequisites", msg.toString(), new String[] {
-//                        "Install Pre-requisites",
-//                        "Ignore Now",
-//                        "Ignore Always",
-//                        "Cancel"
-//                    }, selection);
-//                }
-//                boolean tryInstall = false;
-//                switch (selection) {
-//                    case 0:
-//                        tryInstall = true;
-//                        break;
-//                    case 2:
-//                        ignorePrerequisites.createNewFile();
-//                    case 1:
-//                        break;
-//                    case 3:
-//                    case -1:
-//                        throw new CancelStartupException();
-//                }
-//
-//                if (tryInstall) {
-//                    // Try to install it and check for success afterwards.
-//                    ServerEnvironment.tryInstallDependencies(progressMonitor);
-//                    ServerEnvironment.ensureServerDependenciesMet();
-//                }
-//            } catch (InstallException ie) {
-//                throw new PlatformException(ie);
-//            } catch (ExecutionEnvironmentException eee) {
-//                throw new PlatformException(eee);
-//            } catch (IOException ie) {
-//                throw new PlatformException(ie);
-//            }
-//        }
-//    }
+    /**
+     * Perform normal shutdown for the Simantics Platform.
+     *
+     * @param progressMonitor optional progress monitor
+     * @throws PlatformException
+     * @see {@link #shutdown(IProgressMonitor, boolean)}
+     */
+    public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
+        shutdown(progressMonitor, true);
+    }
 
     /**
      * Shutdown Simantics Platform.
      *
-     * In SWB this is handled in SimanticsWorkbenchAdvisor#disconnectFromWorkspace.
+     * In Simantics Workbench this is handled in
+     * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
      *
-     * @param progressMonitor optional progress monitor
+     * @param progressMonitor
+     *            optional progress monitor
+     * @param clearTemporaryFiles
+     *            allow or prevent deletion of temporary files at the end of the
+     *            shutdown procedure
      * @throws PlatformException
      */
-    public void shutdown(IProgressMonitor progressMonitor) throws PlatformException
+    public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
     {
         SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
         PlatformException platformException = null;
@@ -1002,15 +1026,20 @@ public class SimanticsPlatform implements LifecycleListener {
         }
         progress.worked(10);
 
-        progress.subTask("Clearing Workspace Temporary Directory");
-        try {
-            Simantics.clearTemporaryDirectory();
-        } catch (Throwable t) {
-            LOGGER.error("Failed to clear the temporary directory.", t);
+        if (clearTemporaryFiles) {
+            progress.subTask("Clearing Workspace Temporary Directory");
+            try {
+                Simantics.clearTemporaryDirectory();
+            } catch (Throwable t) {
+                LOGGER.error("Failed to clear the temporary directory.", t);
+            }
         }
         progress.worked(10);
         if (null != platformException)
             throw platformException;
+
+        // #7650: improve shutdown robustness in all applications that use the platform
+        Runtime.getRuntime().removeShutdownHook(shutdownHook);
     }
 
     // TODO: consider removing this in the future ??