X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics%2Fsrc%2Forg%2Fsimantics%2FSimanticsPlatform.java;h=54b96054719e993986a7ab6ad2e938b67fc0e430;hp=11013eaf7e32ffd1f09fbc4477ed074c2dad21e7;hb=8d11ebc5a0d82e3e602d521d29a29e5cd78bc2a1;hpb=5930811a7911090a0c4984380c3b45ed81a93cde diff --git a/bundles/org.simantics/src/org/simantics/SimanticsPlatform.java b/bundles/org.simantics/src/org/simantics/SimanticsPlatform.java index 11013eaf7..54b960547 100644 --- a/bundles/org.simantics/src/org/simantics/SimanticsPlatform.java +++ b/bundles/org.simantics/src/org/simantics/SimanticsPlatform.java @@ -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,8 @@ 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.databoard.Bindings; import org.simantics.databoard.Databoard; import org.simantics.datatypes.literal.Font; @@ -69,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; @@ -79,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; @@ -103,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; @@ -155,6 +168,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; @@ -177,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)} @@ -204,24 +233,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) { @@ -362,21 +398,18 @@ public class SimanticsPlatform implements LifecycleListener { } throw new PlatformException(sb.toString()); } - + List 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; @@ -387,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(); @@ -427,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) @@ -539,38 +576,11 @@ public class SimanticsPlatform implements LifecycleListener { Set publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures(); Collection groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups); - // final List Platform_Features = new ArrayList(); - // - // // Convert graph instances - // Collection platformGraphs = new ArrayList(); - // 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); @@ -682,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. @@ -692,7 +771,8 @@ public class SimanticsPlatform implements LifecycleListener { * installing project features. *

* - * In SWB this is handled in SimanticsWorkbenchAdvisor#openWindows(). + * In Simantics Workbench this is handled in + * SimanticsWorkbenchAdvisor#openWindows(). *

* * If remote server is given, simantics plaform takes connection there @@ -706,7 +786,7 @@ public class SimanticsPlatform implements LifecycleListener { * startup or null 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 { @@ -734,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 /db session = setupDatabase(databaseDriverId, progressMonitor, workspacePolicy, userAgent); TimeLogger.log("Database setup complete"); @@ -749,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); @@ -759,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 @@ -768,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) { @@ -831,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; } @@ -839,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 + * SimanticsWorkbenchAdvisor#disconnectFromWorkspace. * - * @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; @@ -970,6 +1008,7 @@ public class SimanticsPlatform implements LifecycleListener { session = null; projectResource = null; + currentDatabaseDriver = null; DependenciesRelation.assertFinishedTracking(); @@ -987,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 ?? @@ -1032,6 +1076,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); } @@ -1043,5 +1089,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; + } +}