1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
4 * All rights reserved. This program and the accompanying materials
5 * are made available under the terms of the Eclipse Public License v1.0
6 * which accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics;
14 import static org.simantics.db.common.utils.Transaction.commit;
15 import static org.simantics.db.common.utils.Transaction.endTransaction;
16 import static org.simantics.db.common.utils.Transaction.readGraph;
17 import static org.simantics.db.common.utils.Transaction.startTransaction;
18 import static org.simantics.db.common.utils.Transaction.writeGraph;
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.time.Instant;
26 import java.time.ZoneId;
27 import java.time.format.DateTimeFormatter;
28 import java.util.ArrayList;
29 import java.util.Collection;
30 import java.util.HashMap;
31 import java.util.List;
33 import java.util.Map.Entry;
34 import java.util.Properties;
36 import java.util.TreeMap;
37 import java.util.UUID;
38 import java.util.zip.ZipEntry;
39 import java.util.zip.ZipFile;
41 import org.eclipse.core.runtime.ILog;
42 import org.eclipse.core.runtime.IProduct;
43 import org.eclipse.core.runtime.IProgressMonitor;
44 import org.eclipse.core.runtime.IStatus;
45 import org.eclipse.core.runtime.NullProgressMonitor;
46 import org.eclipse.core.runtime.Platform;
47 import org.eclipse.core.runtime.Status;
48 import org.eclipse.core.runtime.SubMonitor;
49 import org.eclipse.osgi.service.resolver.BundleDescription;
51 import org.ini4j.InvalidFileFormatException;
52 import org.simantics.databoard.Bindings;
53 import org.simantics.databoard.Databoard;
54 import org.simantics.datatypes.literal.Font;
55 import org.simantics.datatypes.literal.RGB;
56 import org.simantics.db.Driver;
57 import org.simantics.db.Driver.Management;
58 import org.simantics.db.Manager;
59 import org.simantics.db.ReadGraph;
60 import org.simantics.db.Resource;
61 import org.simantics.db.Session;
62 import org.simantics.db.SessionModel;
63 import org.simantics.db.UndoContext;
64 import org.simantics.db.VirtualGraph;
65 import org.simantics.db.WriteGraph;
66 import org.simantics.db.common.request.ObjectsWithType;
67 import org.simantics.db.common.request.Queries;
68 import org.simantics.db.common.request.WriteRequest;
69 import org.simantics.db.common.request.WriteResultRequest;
70 import org.simantics.db.common.utils.Transaction;
71 import org.simantics.db.exception.ClusterSetExistException;
72 import org.simantics.db.exception.DatabaseException;
73 import org.simantics.db.exception.ResourceNotFoundException;
74 import org.simantics.db.indexing.DatabaseIndexing;
75 import org.simantics.db.layer0.genericrelation.DependenciesRelation;
76 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
77 import org.simantics.db.layer0.util.SimanticsKeys;
78 import org.simantics.db.layer0.util.TGTransferableGraphSource;
79 import org.simantics.db.layer0.variable.VariableRepository;
80 import org.simantics.db.management.SessionContext;
81 import org.simantics.db.request.Read;
82 import org.simantics.db.request.Write;
83 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
84 import org.simantics.db.service.LifecycleSupport.LifecycleState;
85 import org.simantics.db.service.QueryControl;
86 import org.simantics.db.service.UndoRedoSupport;
87 import org.simantics.db.service.VirtualGraphSupport;
88 import org.simantics.db.service.XSupport;
89 import org.simantics.graph.db.GraphDependencyAnalyzer;
90 import org.simantics.graph.db.GraphDependencyAnalyzer.IU;
91 import org.simantics.graph.db.GraphDependencyAnalyzer.IdentityNode;
92 import org.simantics.graph.db.IImportAdvisor;
93 import org.simantics.graph.db.ImportResult;
94 import org.simantics.graph.db.TransferableGraphs;
95 import org.simantics.graph.diff.Diff;
96 import org.simantics.graph.diff.TransferableGraphDelta1;
97 import org.simantics.internal.Activator;
98 import org.simantics.internal.startup.StartupExtensions;
99 import org.simantics.layer0.Layer0;
100 import org.simantics.operation.Layer0X;
101 import org.simantics.project.IProject;
102 import org.simantics.project.ProjectFeatures;
103 import org.simantics.project.ProjectKeys;
104 import org.simantics.project.Projects;
105 import org.simantics.project.exception.ProjectException;
106 import org.simantics.project.features.registry.GroupReference;
107 import org.simantics.project.management.DatabaseManagement;
108 import org.simantics.project.management.GraphBundle;
109 import org.simantics.project.management.GraphBundleEx;
110 import org.simantics.project.management.GraphBundleRef;
111 import org.simantics.project.management.PlatformUtil;
112 import org.simantics.project.management.ServerManager;
113 import org.simantics.project.management.ServerManagerFactory;
114 import org.simantics.project.management.WorkspaceUtil;
115 import org.simantics.utils.FileUtils;
116 import org.simantics.utils.datastructures.Pair;
117 import org.simantics.utils.logging.TimeLogger;
118 import org.simantics.utils.strings.EString;
119 import org.slf4j.Logger;
120 import org.slf4j.LoggerFactory;
123 * SimanticsPlatform performs procedures required in order to get simantics
124 * workbench into operational state. This consists of the following steps:
126 * <li> Asserting there is Database
128 * <li> Starting Database process
130 * <li> Opening a session to Database process
132 * <li> Asserting required ontologies or other transferable graphs are installed in the database
134 * <li> Asserting required project is installed in the database
136 * <li> Asserting Simantics Features are installed in the database
138 * <li> Asserting Simantics Features are installed to the project
140 * <li> Shutdown: Save Session, Close session, Kill Database process
144 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
146 public class SimanticsPlatform implements LifecycleListener {
148 private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsPlatform.class);
151 * The policy is relevant when developing Simantics from Eclipse IDE.
152 * It is applied when the ontology in the database of a workspace doesn't match
153 * a newer ontology in the Eclipse workspace.
155 public static enum OntologyRecoveryPolicy { ThrowError, Merge, ReinstallDatabase }
158 * This policy dictates how the Simantics platform startup should react if
159 * the started workspace is not set up properly. The alternatives are to
160 * just throw an error and fail or to attempt all possible measures to fix
161 * the encountered problems.
163 public static enum RecoveryPolicy { ThrowError, FixError }
165 /** Singleton instance, started in SimanticsWorkbenchAdvisor */
166 public static final SimanticsPlatform INSTANCE = new SimanticsPlatform();
168 /** Set to true when the Simantics Platform is in good-and-go condition */
169 public boolean running;
171 /** ID of the database driver that the platform is currently using */
172 private String currentDatabaseDriver;
174 /** Database Session */
175 public Session session;
176 private Management databasebManagement;
178 /** Database session context */
179 public SessionContext sessionContext;
181 /** Project identifier in Database */
182 public String projectURI;
185 public String projectName;
187 /** Project resource */
188 public Resource projectResource;
190 /** Session specific bindings */
191 public SimanticsBindings simanticsBindings;
192 public SimanticsBindings simanticsBindings2;
194 public Thread mainThread;
196 private Thread shutdownHook = new Thread() {
200 LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
201 shutdown(null, false);
202 } catch (PlatformException e) {
203 LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
204 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
210 * The {@link IProject} activated by
211 * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
213 private IProject project;
218 * Create a new simantics plaform manager in uninitialized state and
219 * with default policies. <p>
221 public SimanticsPlatform() {
222 log = Platform.getLog(Activator.getBundleContext().getBundle());
223 mainThread = Thread.currentThread();
226 public String getApplicationClientId() {
227 IProduct product = Platform.getProduct();
228 if(product == null) return "noProduct";//UUID.randomUUID().toString();
229 String application = product.getApplication();
230 return application != null ? application : UUID.randomUUID().toString();
233 private Session setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
234 if (progressMonitor == null)
235 progressMonitor = new NullProgressMonitor();
236 Path workspaceLocation = Platform.getLocation().toFile().toPath();
237 Path dbLocation = workspaceLocation.resolve("db");
238 Path dbIniPath = workspaceLocation.resolve("db.ini");
239 // The driver file overrides any command line arguments to prevent
240 // using the wrong driver for an existing database directory.
241 ServerManager serverManager;
243 Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
244 databaseDriverId = dbIni.get("driver", "id");
245 serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
246 } catch (DatabaseException | IOException e) {
247 throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
249 progressMonitor.beginTask("Setting up Simantics Database", 100);
250 progressMonitor.setTaskName("Asserting Database is installed.");
251 String msg = "Failed to initialize Simantics database.";
254 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
255 progressMonitor.setTaskName("Creating database at " + dbLocation);
256 databasebManagement = serverManager.getManagement(dbLocation.toFile());
257 databasebManagement.create();
258 currentDatabaseDriver = databaseDriverId;
260 return serverManager.createDatabase(dbLocation.toFile());
261 } catch (DatabaseException e) {
262 throw new PlatformException(msg, e);
263 } catch (Throwable e) {
264 throw new PlatformException(msg, e);
266 progressMonitor.worked(20);
270 public void synchronizeOntologies(IProgressMonitor progressMonitor, OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize) throws PlatformException {
272 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
274 final DatabaseManagement mgmt = new DatabaseManagement();
276 PlatformUtil.compileAllDynamicOntologies();
278 progressMonitor.setTaskName("Asserting all ontologies are installed");
279 final Map<GraphBundleRef, GraphBundleEx> platformTGs = new HashMap<GraphBundleRef, GraphBundleEx>();
282 // Get a list of bundles installed into the database
283 progressMonitor.subTask("find installed bundles from database");
284 Map<GraphBundleRef, GraphBundleEx> installedTGs = new HashMap<GraphBundleRef, GraphBundleEx>();
285 for (GraphBundle b : session.syncRequest( mgmt.GraphBundleQuery )) {
286 installedTGs.put(GraphBundleRef.of(b), GraphBundleEx.extend(b));
289 if(!requireSynchronize && installedTGs.size() > 1 && !Platform.inDevelopmentMode()) return;
290 // if(installedTGs.size() > 1) return;
292 // Get a list of all bundles in the platform (Bundle Context)
293 List<GraphBundle> tgs = new ArrayList<GraphBundle>();
294 progressMonitor.subTask("load all transferable graphs from platform");
295 PlatformUtil.getAllGraphs(tgs);
296 progressMonitor.subTask("extend bundles to compile versions");
297 for (GraphBundle b : tgs) {
298 GraphBundleEx gbe = GraphBundleEx.extend(b);
300 platformTGs.put(GraphBundleRef.of(b), gbe);
303 // Compile a list of TGs that need to be installed or reinstalled in the database
304 progressMonitor.subTask("check bundle reinstallation demand");
305 List<GraphBundleEx> installTGs = new ArrayList<GraphBundleEx>();
306 // Create list of TGs to update, <newTg, oldTg>
307 Map<GraphBundleEx,GraphBundleEx> reinstallTGs = new TreeMap<GraphBundleEx,GraphBundleEx>();
308 for (Entry<GraphBundleRef, GraphBundleEx> e : platformTGs.entrySet()) {
309 GraphBundleRef key = e.getKey();
310 GraphBundleEx platformBundle = e.getValue();
311 GraphBundleEx existingBundle = installedTGs.get(key);
313 // System.out.println("GraphBundleRef key=" + key.toString());
315 if (existingBundle == null) {
316 // Bundle did not exist in the database, put it into list of bundles to install
317 installTGs.add(platformBundle);
320 // Bundle exists in the database
321 boolean platformBundleIsNewer = existingBundle.getVersion().compareTo(platformBundle.getVersion())<0;
322 if (!platformBundleIsNewer)
324 // Check hash of transferable graph to know whether to update or not.
325 if (platformBundle.getHashcode() == existingBundle.getHashcode())
327 //System.out.println("Ontology hashcodes do not match: platform bundle="
328 // + platformBundle.getVersionedId() + ", hash=" + platformBundle.getHashcode()
329 // + "; existing bundle=" + existingBundle.getVersionedId() + ", hash=" + existingBundle.getHashcode());
330 reinstallTGs.put(platformBundle, existingBundle);
334 // Database is missing graphs
335 if (!installTGs.isEmpty() || !reinstallTGs.isEmpty()) {
336 session.getService(XSupport.class).setServiceMode(true, true);
339 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError) {
340 StringBuilder sb = new StringBuilder("The following graphs are not installed in the database: ");
341 if (!installTGs.isEmpty()) {
343 for (GraphBundleEx e : installTGs) {
344 if (i>0) sb.append(", ");
346 sb.append(e.toString());
348 sb.append(" is missing from the database.\n");
350 if (!reinstallTGs.isEmpty()) {
352 for (Entry<GraphBundleEx, GraphBundleEx> e : reinstallTGs.entrySet()) {
353 if (i>0) sb.append(", ");
355 sb.append(e.getKey().toString());
357 sb.append(" Database/Platform Bundle version mismatch.\n");
359 sb.append("Hint: Use -fixErrors to install the graphs.");
360 throw new PlatformException(sb.toString());
362 // Reinstall database
363 if (ontologyPolicy == OntologyRecoveryPolicy.ReinstallDatabase) {
364 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Reinstalling the database."));
373 throw new PlatformException("Reinstalling Database, NOT IMPLEMENTED");
376 if (ontologyPolicy == OntologyRecoveryPolicy.Merge) {
377 progressMonitor.subTask("Merging ontology changes");
378 // Sort missing TGs into install order
379 GraphDependencyAnalyzer<GraphBundle> analyzer = new GraphDependencyAnalyzer<GraphBundle>();
380 for(GraphBundle tg : installTGs) analyzer.addGraph(tg, tg.getGraph());
381 for(GraphBundle tg : reinstallTGs.keySet()) analyzer.addGraph(tg, tg.getGraph());
382 if(!analyzer.analyzeDependency()) {
383 Collection<Pair<GraphBundle, GraphBundle>> problems = analyzer.getConflicts();
384 StringBuilder sb = new StringBuilder();
385 for (Pair<GraphBundle, GraphBundle> problem : problems) {
386 sb.append("Conflict with "+problem.first+" and "+problem.second+".\n");
388 throw new PlatformException(sb.toString());
390 else if(!session.syncRequest( analyzer.queryExternalDependenciesSatisfied )) {
391 Collection<IdentityNode> unsatisfiedDependencies = analyzer.getUnsatisfiedDependencies();
392 StringBuilder sb = new StringBuilder();
393 for (IdentityNode dep: unsatisfiedDependencies) {
394 sb.append("Unsatisfied Dependency "+dep+". Required by\n");
395 for(IU iu : GraphDependencyAnalyzer.toCollection(dep.getRequires())) {
396 sb.append(" " + ((GraphBundle)iu.getId()).getId() + "\n");
399 throw new PlatformException(sb.toString());
402 List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
403 if(!sortedBundles.isEmpty()) {
405 session.syncRequest((Write) graph -> {
407 graph.newClusterSet(graph.getRootLibrary());
408 } catch (ClusterSetExistException e) {
409 // Cluster set exist already, no problem.
411 graph.setClusterSet4NewResource(graph.getRootLibrary());
412 graph.flushCluster();
415 boolean mergedOntologies = false;
418 for(final GraphBundle tg : sortedBundles) {
420 final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
421 final GraphBundle oldTG = reinstallTGs.get(tg);
423 boolean createImmutable = tg.getImmutable();
427 session.getService(XSupport.class).setServiceMode(true, createImmutable);
430 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
431 ImportResult result = TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
432 if (!result.missingExternals.isEmpty()) {
433 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Import of " + tg.toString() + " was missing the following external entities:\n" + EString.implode(result.missingExternals)));
440 startTransaction(session, false);
441 TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
442 final long[] oldResources = oldTG.getResourceArray();
443 boolean changes = TransferableGraphs.hasChanges(readGraph(), oldResources, delta);
446 //log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Nothing to merge for "+tg.toString()));
450 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Merging new version of "+tg.toString()));
452 startTransaction(session, true);
458 long[] resourceArray = TransferableGraphs.applyDelta(writeGraph(), oldResources, delta);
459 tg.setResourceArray(resourceArray);
460 mgmt.setGraphBundleEntry(tg);
462 mergedOntologies = true;
463 } catch (Throwable t) {
464 throw new PlatformException(t);
471 session.syncRequest((Write) graph -> {
472 graph.setClusterSet4NewResource(graph.getRootLibrary());
473 graph.flushCluster();
476 if (mergedOntologies)
477 DatabaseIndexing.deleteAllIndexes();
480 TimeLogger.log("Ontologies synchronized.");
483 session.getService(XSupport.class).setServiceMode(false, false);
485 progressMonitor.worked(20);
486 } catch (IOException e) {
487 throw new PlatformException(e);
488 } catch (DatabaseException e) {
489 throw new PlatformException(e);
494 public boolean assertConfiguration(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy) throws PlatformException {
496 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
498 File workspaceLocation = Platform.getLocation().toFile();
500 boolean installProject = false;
501 progressMonitor.setTaskName("Asserting simantics.cfg is installed");
503 File propertyFile = new File(workspaceLocation, "simantics.cfg");
504 Properties properties;
506 properties = WorkspaceUtil.readProperties(propertyFile);
507 } catch (IOException e) {
508 if (workspacePolicy == RecoveryPolicy.ThrowError) throw new PlatformException("Could not load "+propertyFile);
510 // Create a project and write Property file
511 properties = new Properties();
512 properties.setProperty("project_uri", "http://Projects/Development%20Project");
513 properties.setProperty("project_name", "Development Project");
514 WorkspaceUtil.writeProperties(propertyFile, properties);
515 installProject |= true;
517 projectURI = properties.getProperty("project_uri");
518 projectName = properties.getProperty("project_name");
519 progressMonitor.worked(10);
520 } catch (IOException e) {
521 throw new PlatformException(e);
524 return installProject;
528 public boolean assertProject(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, boolean installProject) throws PlatformException {
530 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
532 final DatabaseManagement mgmt = new DatabaseManagement();
534 progressMonitor.setTaskName("Asserting project resource exists in the database");
536 projectResource = session.syncRequest( Queries.resource( projectURI ) );
537 } catch (ResourceNotFoundException nfe) {
538 // Project was not found
539 if (workspacePolicy == RecoveryPolicy.ThrowError)
540 throw new PlatformException("Project Resource "+projectURI+" is not found in the database.");
541 // Create empty project with no features
543 Transaction.startTransaction(session, true);
545 // The project needs to be created mutable.
546 session.getService(XSupport.class).setServiceMode(true, false);
548 ArrayList<String> empty = new ArrayList<String>();
549 projectResource = mgmt.createProject(projectName, empty);
550 installProject |= true;
552 session.getService(XSupport.class).setServiceMode(false, false);
553 Transaction.commit();
555 Transaction.endTransaction();
557 //session.getService( LifecycleSupport.class ).save();
558 } catch (DatabaseException e) {
559 throw new PlatformException("Failed to create "+projectURI, e);
561 } catch (DatabaseException e) {
562 throw new PlatformException("Failed to create "+projectURI, e);
564 progressMonitor.worked(10);
566 return installProject;
570 public void updateInstalledGroups(IProgressMonitor progressMonitor, boolean installProject) throws PlatformException {
574 // Attach all feature groups available in platform to created project
575 progressMonitor.setTaskName("Install all features");
576 Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
577 Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
582 Projects.setProjectInstalledGroups(graph, projectResource, groupsWithoutVersion));
583 } catch (DatabaseException ae) {
584 throw new PlatformException("Failed to install features", ae);
586 progressMonitor.worked(10);
591 public void assertSessionModel(IProgressMonitor progressMonitor) throws PlatformException {
593 Properties properties = session.getService(Properties.class);
594 final String clientId = properties.getProperty("clientId");
598 // Currently this needs to be done before data becomes available
599 VirtualGraphSupport support = session.getService(VirtualGraphSupport.class);
600 VirtualGraph activations = support.getWorkspacePersistent("activations");
602 Resource sessionModel = session.syncRequest(new Read<Resource>() {
605 public Resource perform(ReadGraph graph) throws DatabaseException {
607 Layer0X L0X = Layer0X.getInstance(graph);
608 for(Resource sessionModel : graph.syncRequest(new ObjectsWithType(graph.getRootLibrary(), L0X.HasSession, L0X.Session))) {
609 String id = graph.getPossibleRelatedValue(sessionModel, L0X.Session_HasClientId);
610 if(id != null && id.equals(clientId)) return sessionModel;
618 if(sessionModel == null) {
620 sessionModel = session.syncRequest(new WriteResultRequest<Resource>(activations) {
623 public Resource perform(WriteGraph graph) throws DatabaseException {
624 Layer0 L0 = Layer0.getInstance(graph);
625 Layer0X L0X = Layer0X.getInstance(graph);
626 Resource session = graph.newResource();
627 graph.claim(session, L0.InstanceOf, null, L0X.Session);
628 graph.claim(session, L0X.Session_HasUser, null, graph.getResource("http://Users/AdminUser"));
629 graph.addLiteral(session, L0X.Session_HasClientId, L0X.Session_HasClientId_Inverse, clientId, Bindings.STRING);
630 graph.claim(graph.getRootLibrary(), L0X.HasSession, session);
637 session.registerService(SessionModel.class, new PlatformSessionModel(sessionModel));
638 } catch (DatabaseException e) {
639 throw new PlatformException(e);
644 static class PlatformSessionModel implements SessionModel {
645 private final Resource sessionModel;
647 public PlatformSessionModel(Resource model) {
648 this.sessionModel = model;
652 public Resource getResource() {
657 public void resetDatabase(IProgressMonitor monitor) throws PlatformException {
658 File dbLocation = Platform.getLocation().append("db").toFile();
659 if(!dbLocation.exists()) return;
661 Driver driver = Manager.getDriver("procore");
662 Management management = driver.getManagement(dbLocation.getAbsolutePath(), null);
664 } catch (DatabaseException e) {
665 throw new PlatformException("Failed to remove database at " + dbLocation.getAbsolutePath(), e);
667 // We have created extra files to database folder which have to be deleted also.
668 // This is an awful idea! Do not create extra files to database folder!
670 for (int i=0; i<10; ++i) {
672 FileUtils.deleteAll(dbLocation);
675 } catch (IOException e) {
676 // Assuming this has been thrown because delete file/folder failed.
681 } catch (InterruptedException e) {
682 // Ignoring interrupted exception.
686 throw new PlatformException("Failed to remove database folder at " + dbLocation.getAbsolutePath(), t);
688 public void resetWorkspace(IProgressMonitor monitor, ArrayList<String> fileFilter) throws PlatformException, IllegalStateException, IOException {
689 File file = Platform.getLocation().toFile();
690 if (null != fileFilter)
691 FileUtils.deleteAllWithFilter(file , fileFilter);
692 resetDatabase(monitor);
695 public boolean handleBaselineDatabase() throws PlatformException {
696 Path workspaceLocation = Platform.getLocation().toFile().toPath();
697 Path baselineIndicatorFile = workspaceLocation.resolve(".baselined");
698 if (Files.isRegularFile(baselineIndicatorFile)) {
699 // This means that the workspace has already been initialized from
700 // a database baseline and further initialization is not necessary.
704 String dbBaselineArchive = System.getProperty("org.simantics.db.baseline", null);
705 if (dbBaselineArchive == null)
708 Path baseline = Paths.get(dbBaselineArchive);
709 if (!Files.isRegularFile(baseline))
710 throw new PlatformException("Specified database baseline archive " + baseline + " does not exist. Cannot initialize workspace database.");
712 validateBaselineFile(baseline);
713 validateWorkspaceForBaselineInitialization(workspaceLocation);
716 Files.createDirectories(workspaceLocation);
717 FileUtils.extractZip(baseline.toFile(), workspaceLocation.toFile());
718 Files.write(baselineIndicatorFile, baselineIndicatorContents(baselineIndicatorFile));
720 } catch (IOException e) {
721 throw new PlatformException(e);
725 private static final DateTimeFormatter TIMESTAMP_FORMAT = DateTimeFormatter.ofPattern("d. MMM yyyy HH:mm:ss");
727 private static byte[] baselineIndicatorContents(Path path) throws IOException {
728 return String.format("%s%n%s%n",
730 Instant.now().atZone(ZoneId.systemDefault()).format(TIMESTAMP_FORMAT))
734 private void validateWorkspaceForBaselineInitialization(Path workspaceLocation) throws PlatformException {
736 Path db = workspaceLocation.resolve("db");
737 if (Files.exists(db))
738 throw new PlatformException("Database location " + db + " already exists. Cannot re-initialize workspace from baseline.");
739 Path index = workspaceLocation.resolve(".metadata/.plugins/org.simantics.db.indexing");
740 if (!Files.exists(index) || !isEmptyDirectory(index))
741 throw new PlatformException("Index location " + index + " already exists. Cannot re-initialize workspace from baseline.");
742 } catch (IOException e) {
743 throw new PlatformException("Failed to validate workspace for baseline initialization", e);
747 private static boolean isEmptyDirectory(Path dir) throws IOException {
748 return Files.walk(dir).count() == 1;
751 private void validateBaselineFile(Path baseline) throws PlatformException {
752 try (ZipFile zip = new ZipFile(baseline.toFile())) {
753 ZipEntry db = zip.getEntry("db");
755 throw new PlatformException("Baseline archive does not contain database directory 'db'");
756 ZipEntry index = zip.getEntry(".metadata/.plugins/org.simantics.db.indexing");
758 throw new PlatformException("Baseline archive does not contain database index directory '.metadata/.plugins/org.simantics.db.indexing'");
759 } catch (IOException e) {
760 throw new PlatformException("Failed to validate baseline archive " + baseline, e);
765 * Start-up the platform. The procedure consists of 8 steps. Once everything
766 * is up and running, all fields are set property.
769 * If workspacePolicy is FixErrors, there is an attempt to fix unexpected
770 * errors. It includes installing database files, installing ontologies, and
771 * installing project features.
774 * In Simantics Workbench this is handled in
775 * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
778 * If remote server is given, simantics plaform takes connection there
779 * instead of local server at "db/".
781 * @param workspacePolicy action to take on workspace/database related
783 * @param ontologyPolicy action to take on ontology mismatch
784 * @param progressMonitor optional progress monitor
785 * @param userAgent interface for resorting to user feedback during platform
786 * startup or <code>null</code> to resort to default measures
787 * @throws PlatformException
789 public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
790 OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
791 throws PlatformException
795 TimeLogger.log("Beginning of SimanticsPlatform.startUp");
797 LOGGER.info("Beginning of SimanticsPlatform.startUp");
799 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
801 // For debugging on what kind of platform automatic tests are running in
802 // case there are problems.
803 if ("true".equals(System.getProperty("org.simantics.dumpBundleState")))
804 dumpPlatformBundleState();
806 // 0. Consult all startup extensions before doing anything with the workspace.
807 StartupExtensions.consultStartupExtensions();
808 TimeLogger.log("Consulted platform pre-startup extensions");
810 // 0.1. Clear all temporary files
811 Simantics.clearTemporaryDirectory();
812 TimeLogger.log("Cleared temporary directory");
814 // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
815 VariableRepository.clear();
817 // 0.3 Handle baseline database before opening db
818 boolean usingBaseline = handleBaselineDatabase();
820 // 1. Assert there is a database at <workspace>/db
821 session = setupDatabase(databaseDriverId, progressMonitor, workspacePolicy, userAgent);
822 TimeLogger.log("Database setup complete");
825 XSupport support = session.getService(XSupport.class);
826 if (support.rolledback()) {
828 DatabaseIndexing.deleteAllIndexes();
829 } catch (IOException e) {
830 throw new PlatformException(e);
834 // 2. Assert all graphs, and correct versions, are installed to the database
836 synchronizeOntologies(progressMonitor, ontologyPolicy, requireSynchronize);
837 TimeLogger.log("Synchronized ontologies");
840 // 4. Assert simantics.cfg exists
841 boolean installProject = assertConfiguration(progressMonitor,workspacePolicy);
843 // 5. Assert Project Resource is installed in the database
844 installProject = assertProject(progressMonitor, workspacePolicy, installProject);
846 // 6. Install all features into project, if in debug mode
847 updateInstalledGroups(progressMonitor, true); //installProject);
848 TimeLogger.log("Installed all features into project");
850 // 7. Assert L0.Session in database for this session
851 assertSessionModel(progressMonitor);
853 session.getService(XSupport.class).setServiceMode(false, false);
856 session.syncRequest((Write) graph -> {
857 QueryControl qc = graph.getService(QueryControl.class);
860 TimeLogger.log("Flushed queries");
861 } catch (DatabaseException e) {
862 LOGGER.error("Flushing queries failed.", e);
864 boolean loadProject = true;
867 sessionContext = SimanticsPlatform.INSTANCE.createSessionContext(true);
868 // This must be before setSessionContext since some listeners might query this
869 sessionContext.setHint(SimanticsKeys.KEY_PROJECT, SimanticsPlatform.INSTANCE.projectResource);
871 Simantics.setSessionContext(sessionContext);
873 // 1. Put ResourceBinding that throws an exception to General Bindings
874 simanticsBindings = new SimanticsBindings( null );
875 Bindings.classBindingFactory.addFactory( simanticsBindings );
878 // 2. Create session-specific second Binding context (Databoard) and
879 // put that to Session as a service
880 Session session = sessionContext.getSession();
881 Databoard sessionDataboard = new Databoard();
882 session.registerService(Databoard.class, sessionDataboard);
883 simanticsBindings2 = new SimanticsBindings( session );
884 sessionDataboard.classBindingFactory.addFactory( simanticsBindings2 );
886 // Register datatype bindings
887 Bindings.defaultBindingFactory.getRepository().put(RGB.Integer.BINDING.type(), RGB.Integer.BINDING);
888 Bindings.defaultBindingFactory.getRepository().put(Font.BINDING.type(), Font.BINDING);
892 TimeLogger.log("Load projects");
893 project = Projects.loadProject(sessionContext.getSession(), SimanticsPlatform.INSTANCE.projectResource);
895 sessionContext.setHint(ProjectKeys.KEY_PROJECT, project);
896 TimeLogger.log("Loading projects complete");
899 TimeLogger.log("Project activated");
902 } catch (DatabaseException e) {
903 LOGGER.error("Platform startup failed.", e);
904 throw new PlatformException(e);
905 } catch (ProjectException e) {
906 boolean hasStackTrace = e.getStackTrace().length > 0;
908 throw new PlatformException(e.getMessage(), hasStackTrace);
909 throw new PlatformException(e, hasStackTrace);
914 // #7650: improve shutdown robustness in all applications that use the platform
915 Runtime.getRuntime().addShutdownHook(shutdownHook);
917 return sessionContext;
921 public SessionContext createSessionContext(boolean init) throws PlatformException {
923 // Construct and initialize SessionContext from Session.
924 SessionContext sessionContext = SessionContext.create(session, init);
925 TimeLogger.log("Session context created");
927 sessionContext.registerServices();
928 TimeLogger.log("Session services registered");
930 return sessionContext;
931 } catch (DatabaseException e) {
932 throw new PlatformException(e);
937 * Perform normal shutdown for the Simantics Platform.
939 * @param progressMonitor optional progress monitor
940 * @throws PlatformException
941 * @see {@link #shutdown(IProgressMonitor, boolean)}
943 public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
944 shutdown(progressMonitor, true);
948 * Shutdown Simantics Platform.
950 * In Simantics Workbench this is handled in
951 * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
953 * @param progressMonitor
954 * optional progress monitor
955 * @param clearTemporaryFiles
956 * allow or prevent deletion of temporary files at the end of the
958 * @throws PlatformException
960 public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
962 SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
963 PlatformException platformException = null;
965 progress.subTask("Close Project");
966 if (project != null) {
967 project.safeDispose();
972 progress.subTask("Close Database Session");
973 Databoard databoard = null;
974 if (sessionContext != null) {
975 Session s = sessionContext.peekSession();
977 databoard = s.peekService(Databoard.class);
979 progress.subTask("Flushing Index Caches");
981 Simantics.flushIndexCaches(progress.newChild(20), s);
982 } catch (Throwable t) {
983 LOGGER.error("Failed to flush index caches.", t);
987 progress.subTask("Close Database Session");
988 sessionContext.safeDispose();
989 sessionContext = null;
990 Simantics.setSessionContext(null);
992 if (simanticsBindings != null) {
993 Bindings.classBindingFactory.removeFactory( simanticsBindings );
994 simanticsBindings = null;
996 if (databoard != null) {
997 if (simanticsBindings2 != null) {
998 databoard.classBindingFactory.removeFactory( simanticsBindings2 );
999 simanticsBindings2 = null;
1004 // Make sure Simantics clipboard doesn't store unwanted session data references.
1005 Simantics.setClipboard(new SimanticsClipboardImpl());
1007 progress.worked(30);
1010 projectResource = null;
1011 currentDatabaseDriver = null;
1013 DependenciesRelation.assertFinishedTracking();
1015 } catch (Exception e) {
1016 platformException = new PlatformException("Failed to shutdown Simantics Platform", e);
1019 progress.worked(10);
1020 progress.subTask("Shutting down database");
1022 if (null != databasebManagement)
1023 databasebManagement.shutdown();
1024 } catch (Throwable t) {
1025 LOGGER.error("Database shutdown failed.", t);
1027 progress.worked(10);
1029 if (clearTemporaryFiles) {
1030 progress.subTask("Clearing Workspace Temporary Directory");
1032 Simantics.clearTemporaryDirectory();
1033 } catch (Throwable t) {
1034 LOGGER.error("Failed to clear the temporary directory.", t);
1037 progress.worked(10);
1038 if (null != platformException)
1039 throw platformException;
1041 // #7650: improve shutdown robustness in all applications that use the platform
1042 Runtime.getRuntime().removeShutdownHook(shutdownHook);
1045 // TODO: consider removing this in the future ??
1047 public void stateChanged(LifecycleState newState) {
1048 if(newState == LifecycleState.CLOSED) {
1050 if(Platform.isRunning()) {
1051 mainThread.interrupt();
1058 * @return <code>true</code> if discard was successful, <code>false</code>
1059 * if there was no session, {@link UndoRedoSupport} or
1060 * {@link UndoContext} to discard through
1062 public boolean discardSessionUndoHistory() {
1063 Session s = session;
1065 UndoRedoSupport urs = s.peekService(UndoRedoSupport.class);
1067 UndoContext uc = urs.getUndoContext(s);
1077 public void reconnect(String databaseDriverId) throws Exception {
1078 // Starts database server.
1079 if (currentDatabaseDriver != null)
1080 databaseDriverId = currentDatabaseDriver;
1081 SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
1084 private void dumpPlatformBundleState() {
1085 BundleDescription[] bs = Platform.getPlatformAdmin().getState().getBundles();
1086 System.out.println("Total bundles: " + bs.length);
1087 for (BundleDescription b : bs) {
1088 System.out.format("%-80s @ %s\n", b.toString(), b.getLocation());
1092 private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
1093 throws InvalidFileFormatException, IOException
1095 File f = path.toFile();
1096 Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
1097 String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
1098 if (iniId == null) {
1099 dbIni.put("driver", "id", databaseDriverId);