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;
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;
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;
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;
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;
/** 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;
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)}
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) {
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();
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 {
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.
* 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
* 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
{
// 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");
}
// 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);
running = true;
+ // #7650: improve shutdown robustness in all applications that use the platform
+ Runtime.getRuntime().addShutdownHook(shutdownHook);
+
return sessionContext;
}
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;
session = null;
projectResource = null;
+ currentDatabaseDriver = null;
DependenciesRelation.assertFinishedTracking();
}
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 ??
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);
}
}
}
-}
+ 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;
+ }
+}