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.util.ArrayList;
26 import java.util.Collection;
27 import java.util.HashMap;
28 import java.util.List;
30 import java.util.Map.Entry;
31 import java.util.Properties;
33 import java.util.TreeMap;
34 import java.util.UUID;
36 import org.eclipse.core.runtime.ILog;
37 import org.eclipse.core.runtime.IProduct;
38 import org.eclipse.core.runtime.IProgressMonitor;
39 import org.eclipse.core.runtime.IStatus;
40 import org.eclipse.core.runtime.NullProgressMonitor;
41 import org.eclipse.core.runtime.Platform;
42 import org.eclipse.core.runtime.Status;
43 import org.eclipse.core.runtime.SubMonitor;
44 import org.eclipse.osgi.service.resolver.BundleDescription;
46 import org.ini4j.InvalidFileFormatException;
47 import org.simantics.databoard.Bindings;
48 import org.simantics.databoard.Databoard;
49 import org.simantics.datatypes.literal.Font;
50 import org.simantics.datatypes.literal.RGB;
51 import org.simantics.db.Driver;
52 import org.simantics.db.Driver.Management;
53 import org.simantics.db.Manager;
54 import org.simantics.db.ReadGraph;
55 import org.simantics.db.Resource;
56 import org.simantics.db.Session;
57 import org.simantics.db.SessionModel;
58 import org.simantics.db.UndoContext;
59 import org.simantics.db.VirtualGraph;
60 import org.simantics.db.WriteGraph;
61 import org.simantics.db.common.request.ObjectsWithType;
62 import org.simantics.db.common.request.Queries;
63 import org.simantics.db.common.request.WriteResultRequest;
64 import org.simantics.db.common.utils.Transaction;
65 import org.simantics.db.exception.ClusterSetExistException;
66 import org.simantics.db.exception.DatabaseException;
67 import org.simantics.db.exception.ResourceNotFoundException;
68 import org.simantics.db.indexing.DatabaseIndexing;
69 import org.simantics.db.layer0.genericrelation.DependenciesRelation;
70 import org.simantics.db.layer0.genericrelation.IndexException;
71 import org.simantics.db.layer0.genericrelation.IndexedRelations;
72 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
73 import org.simantics.db.layer0.util.SimanticsKeys;
74 import org.simantics.db.layer0.util.TGTransferableGraphSource;
75 import org.simantics.db.layer0.variable.VariableRepository;
76 import org.simantics.db.management.SessionContext;
77 import org.simantics.db.request.Read;
78 import org.simantics.db.request.Write;
79 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
80 import org.simantics.db.service.LifecycleSupport.LifecycleState;
81 import org.simantics.db.service.QueryControl;
82 import org.simantics.db.service.UndoRedoSupport;
83 import org.simantics.db.service.VirtualGraphSupport;
84 import org.simantics.db.service.XSupport;
85 import org.simantics.graph.db.GraphDependencyAnalyzer;
86 import org.simantics.graph.db.GraphDependencyAnalyzer.IU;
87 import org.simantics.graph.db.GraphDependencyAnalyzer.IdentityNode;
88 import org.simantics.graph.db.IImportAdvisor;
89 import org.simantics.graph.db.ImportResult;
90 import org.simantics.graph.db.TransferableGraphs;
91 import org.simantics.graph.diff.Diff;
92 import org.simantics.graph.diff.TransferableGraphDelta1;
93 import org.simantics.internal.Activator;
94 import org.simantics.internal.startup.StartupExtensions;
95 import org.simantics.layer0.Layer0;
96 import org.simantics.operation.Layer0X;
97 import org.simantics.project.IProject;
98 import org.simantics.project.ProjectFeatures;
99 import org.simantics.project.ProjectKeys;
100 import org.simantics.project.Projects;
101 import org.simantics.project.SessionDescriptor;
102 import org.simantics.project.exception.ProjectException;
103 import org.simantics.project.features.registry.GroupReference;
104 import org.simantics.project.management.DatabaseManagement;
105 import org.simantics.project.management.GraphBundle;
106 import org.simantics.project.management.GraphBundleEx;
107 import org.simantics.project.management.GraphBundleRef;
108 import org.simantics.project.management.PlatformUtil;
109 import org.simantics.project.management.ServerManager;
110 import org.simantics.project.management.ServerManagerFactory;
111 import org.simantics.project.management.WorkspaceUtil;
112 import org.simantics.utils.FileUtils;
113 import org.simantics.utils.datastructures.Pair;
114 import org.simantics.utils.logging.TimeLogger;
115 import org.simantics.utils.strings.EString;
116 import org.slf4j.Logger;
117 import org.slf4j.LoggerFactory;
120 * SimanticsPlatform performs procedures required in order to get simantics
121 * workbench into operational state. This consists of the following steps:
123 * <li> Asserting there is Database
125 * <li> Starting Database process
127 * <li> Opening a session to Database process
129 * <li> Asserting required ontologies or other transferable graphs are installed in the database
131 * <li> Asserting required project is installed in the database
133 * <li> Asserting Simantics Features are installed in the database
135 * <li> Asserting Simantics Features are installed to the project
137 * <li> Shutdown: Save Session, Close session, Kill Database process
141 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
143 public class SimanticsPlatform implements LifecycleListener {
145 private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsPlatform.class);
148 * The policy is relevant when developing Simantics from Eclipse IDE.
149 * It is applied when the ontology in the database of a workspace doesn't match
150 * a newer ontology in the Eclipse workspace.
152 public static enum OntologyRecoveryPolicy { ThrowError, Merge, ReinstallDatabase }
155 * This policy dictates how the Simantics platform startup should react if
156 * the started workspace is not set up properly. The alternatives are to
157 * just throw an error and fail or to attempt all possible measures to fix
158 * the encountered problems.
160 public static enum RecoveryPolicy { ThrowError, FixError }
162 /** Singleton instance, started in SimanticsWorkbenchAdvisor */
163 public static final SimanticsPlatform INSTANCE = new SimanticsPlatform();
165 /** Set to true when the Simantics Platform is in good-and-go condition */
166 public boolean running;
168 /** ID of the database driver that the platform is currently using */
169 private String currentDatabaseDriver;
171 /** Database Session */
172 public Session session;
173 private Management databasebManagement;
175 /** Database session context */
176 public SessionContext sessionContext;
178 /** Project identifier in Database */
179 public String projectURI;
182 public String projectName;
184 /** Project resource */
185 public Resource projectResource;
187 /** Session specific bindings */
188 public SimanticsBindings simanticsBindings;
189 public SimanticsBindings simanticsBindings2;
191 public Thread mainThread;
193 private Thread shutdownHook = new Thread() {
197 LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
198 shutdown(null, false);
199 } catch (PlatformException e) {
200 LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
201 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
207 * The {@link IProject} activated by
208 * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
210 private IProject project;
215 * Create a new simantics plaform manager in uninitialized state and
216 * with default policies. <p>
218 public SimanticsPlatform() {
219 log = Platform.getLog(Activator.getBundleContext().getBundle());
220 mainThread = Thread.currentThread();
223 public String getApplicationClientId() {
224 IProduct product = Platform.getProduct();
225 if(product == null) return "noProduct";//UUID.randomUUID().toString();
226 String application = product.getApplication();
227 return application != null ? application : UUID.randomUUID().toString();
230 private SessionDescriptor setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
231 if (progressMonitor == null)
232 progressMonitor = new NullProgressMonitor();
233 Path workspaceLocation = Platform.getLocation().toFile().toPath();
234 Path dbLocation = workspaceLocation.resolve("db");
235 Path dbIniPath = workspaceLocation.resolve("db.ini");
236 // The driver file overrides any command line arguments to prevent
237 // using the wrong driver for an existing database directory.
238 ServerManager serverManager;
240 Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
241 databaseDriverId = dbIni.get("driver", "id");
242 serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
243 } catch (DatabaseException | IOException e) {
244 throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
246 progressMonitor.beginTask("Setting up Simantics Database", 100);
247 progressMonitor.setTaskName("Asserting Database is installed.");
248 String msg = "Failed to initialize Simantics database.";
251 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
252 progressMonitor.setTaskName("Creating database at " + dbLocation);
253 databasebManagement = serverManager.getManagement(dbLocation.toFile());
254 databasebManagement.create();
255 currentDatabaseDriver = databaseDriverId;
257 return serverManager.createDatabase(dbLocation.toFile());
258 } catch (DatabaseException e) {
259 throw new PlatformException(msg, e);
260 } catch (Throwable e) {
261 throw new PlatformException(msg, e);
263 progressMonitor.worked(20);
267 public void synchronizeOntologies(IProgressMonitor progressMonitor, OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize) throws PlatformException {
269 SubMonitor monitor = SubMonitor.convert(progressMonitor, 100);
271 monitor.setTaskName("Compile dynamic ontologies");
272 PlatformUtil.compileAllDynamicOntologies();
274 String message = "Asserting all ontologies are installed";
275 LOGGER.info(message);
276 monitor.setTaskName(message);
278 DatabaseManagement mgmt = new DatabaseManagement();
279 Map<GraphBundleRef, GraphBundleEx> platformTGs = new HashMap<>();
282 // Get a list of bundles installed into the database
283 message = "find installed bundles from database";
284 monitor.subTask(message);
285 LOGGER.info(message);
286 Map<GraphBundleRef, GraphBundleEx> installedTGs = new HashMap<>();
287 for (GraphBundle b : session.syncRequest( mgmt.GraphBundleQuery )) {
288 installedTGs.put(GraphBundleRef.of(b), GraphBundleEx.extend(b));
291 if(!requireSynchronize && installedTGs.size() > 1 && !Platform.inDevelopmentMode()) return;
292 // if(installedTGs.size() > 1) return;
294 // Get a list of all bundles in the platform (Bundle Context)
295 message = "load all transferable graphs from platform";
296 monitor.subTask(message);
297 LOGGER.info(message);
298 Collection<GraphBundle> tgs = PlatformUtil.getAllGraphs();
299 message = "extend bundles to compile versions";
300 monitor.subTask(message);
301 LOGGER.info(message);
302 for (GraphBundle b : tgs) {
303 GraphBundleEx gbe = GraphBundleEx.extend(b);
305 platformTGs.put(GraphBundleRef.of(b), gbe);
308 // Compile a list of TGs that need to be installed or reinstalled in the database
309 message = "check bundle reinstallation demand";
310 monitor.subTask(message);
311 LOGGER.info(message);
312 List<GraphBundleEx> installTGs = new ArrayList<>();
313 // Create list of TGs to update, <newTg, oldTg>
314 Map<GraphBundleEx,GraphBundleEx> reinstallTGs = new TreeMap<>();
315 for (Entry<GraphBundleRef, GraphBundleEx> e : platformTGs.entrySet()) {
316 GraphBundleRef key = e.getKey();
317 GraphBundleEx platformBundle = e.getValue();
318 GraphBundleEx existingBundle = installedTGs.get(key);
320 // System.out.println("GraphBundleRef key=" + key.toString());
322 if (existingBundle == null) {
323 // Bundle did not exist in the database, put it into list of bundles to install
324 installTGs.add(platformBundle);
327 // Bundle exists in the database
328 boolean platformBundleIsNewer = existingBundle.getVersion().compareTo(platformBundle.getVersion())<0;
329 if (!platformBundleIsNewer)
331 // Check hash of transferable graph to know whether to update or not.
332 if (platformBundle.getHashcode() == existingBundle.getHashcode())
334 //System.out.println("Ontology hashcodes do not match: platform bundle="
335 // + platformBundle.getVersionedId() + ", hash=" + platformBundle.getHashcode()
336 // + "; existing bundle=" + existingBundle.getVersionedId() + ", hash=" + existingBundle.getHashcode());
337 reinstallTGs.put(platformBundle, existingBundle);
341 // Database is missing graphs
342 if (!installTGs.isEmpty() || !reinstallTGs.isEmpty()) {
343 session.getService(XSupport.class).setServiceMode(true, true);
346 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError) {
347 StringBuilder sb = new StringBuilder("The following graphs are not installed in the database: ");
348 if (!installTGs.isEmpty()) {
350 for (GraphBundleEx e : installTGs) {
351 if (i>0) sb.append(", ");
353 sb.append(e.toString());
355 sb.append(" is missing from the database.\n");
357 if (!reinstallTGs.isEmpty()) {
359 for (Entry<GraphBundleEx, GraphBundleEx> e : reinstallTGs.entrySet()) {
360 if (i>0) sb.append(", ");
362 sb.append(e.getKey().toString());
364 sb.append(" Database/Platform Bundle version mismatch.\n");
366 sb.append("Hint: Use -fixErrors to install the graphs.");
367 throw new PlatformException(sb.toString());
369 // Reinstall database
370 if (ontologyPolicy == OntologyRecoveryPolicy.ReinstallDatabase) {
371 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Reinstalling the database."));
380 throw new PlatformException("Reinstalling Database, NOT IMPLEMENTED");
383 if (ontologyPolicy == OntologyRecoveryPolicy.Merge) {
384 message = "Merging ontology changes";
385 monitor.subTask(message);
386 LOGGER.info(message);
387 // Sort missing TGs into install order
388 GraphDependencyAnalyzer<GraphBundle> analyzer = new GraphDependencyAnalyzer<GraphBundle>();
389 for(GraphBundle tg : installTGs) analyzer.addGraph(tg, tg.getGraph());
390 for(GraphBundle tg : reinstallTGs.keySet()) analyzer.addGraph(tg, tg.getGraph());
391 if(!analyzer.analyzeDependency()) {
392 Collection<Pair<GraphBundle, GraphBundle>> problems = analyzer.getConflicts();
393 StringBuilder sb = new StringBuilder();
394 for (Pair<GraphBundle, GraphBundle> problem : problems) {
395 sb.append("Conflict with "+problem.first+" and "+problem.second+".\n");
397 throw new PlatformException(sb.toString());
399 else if(!session.syncRequest( analyzer.queryExternalDependenciesSatisfied )) {
400 Collection<IdentityNode> unsatisfiedDependencies = analyzer.getUnsatisfiedDependencies();
401 StringBuilder sb = new StringBuilder();
402 for (IdentityNode dep: unsatisfiedDependencies) {
403 sb.append("Unsatisfied Dependency "+dep+". Required by\n");
404 for(IU iu : GraphDependencyAnalyzer.toCollection(dep.getRequires())) {
405 sb.append(" " + ((GraphBundle)iu.getId()).getId() + "\n");
408 throw new PlatformException(sb.toString());
411 List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
412 if(!sortedBundles.isEmpty()) {
414 session.syncRequest((Write) graph -> {
416 graph.newClusterSet(graph.getRootLibrary());
417 } catch (ClusterSetExistException e) {
418 // Cluster set exist already, no problem.
420 graph.setClusterSet4NewResource(graph.getRootLibrary());
421 graph.flushCluster();
424 boolean mergedOntologies = false;
427 for(final GraphBundle tg : sortedBundles) {
429 final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
430 final GraphBundle oldTG = reinstallTGs.get(tg);
432 boolean createImmutable = tg.getImmutable();
436 session.getService(XSupport.class).setServiceMode(true, createImmutable);
439 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
440 ImportResult result = TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
441 if (!result.missingExternals.isEmpty()) {
442 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Import of " + tg.toString() + " was missing the following external entities:\n" + EString.implode(result.missingExternals)));
449 startTransaction(session, false);
450 TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
451 final long[] oldResources = oldTG.getResourceArray();
452 boolean changes = TransferableGraphs.hasChanges(readGraph(), oldResources, delta);
455 //log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Nothing to merge for "+tg.toString()));
459 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Merging new version of "+tg.toString()));
461 startTransaction(session, true);
465 long[] resourceArray = TransferableGraphs.applyDelta(writeGraph(), oldResources, delta);
466 tg.setResourceArray(resourceArray);
467 mgmt.setGraphBundleEntry(tg);
469 mergedOntologies = true;
470 } catch (Throwable t) {
471 throw new PlatformException(t);
478 session.syncRequest((Write) graph -> {
479 graph.setClusterSet4NewResource(graph.getRootLibrary());
480 graph.flushCluster();
483 if (mergedOntologies)
484 DatabaseIndexing.deleteAllIndexes();
487 session.getService(XSupport.class).setServiceMode(false, false);
490 } catch (IOException e) {
491 throw new PlatformException(e);
492 } catch (DatabaseException e) {
493 throw new PlatformException(e);
498 public boolean assertConfiguration(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy) throws PlatformException {
500 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
502 File workspaceLocation = Platform.getLocation().toFile();
504 boolean installProject = false;
505 progressMonitor.setTaskName("Asserting simantics.cfg is installed");
507 File propertyFile = new File(workspaceLocation, "simantics.cfg");
508 Properties properties;
510 properties = WorkspaceUtil.readProperties(propertyFile);
511 } catch (IOException e) {
512 if (workspacePolicy == RecoveryPolicy.ThrowError) throw new PlatformException("Could not load "+propertyFile);
514 // Create a project and write Property file
515 properties = new Properties();
516 properties.setProperty("project_uri", "http://Projects/Development%20Project");
517 properties.setProperty("project_name", "Development Project");
518 WorkspaceUtil.writeProperties(propertyFile, properties);
519 installProject |= true;
521 projectURI = properties.getProperty("project_uri");
522 projectName = properties.getProperty("project_name");
523 progressMonitor.worked(10);
524 } catch (IOException e) {
525 throw new PlatformException(e);
528 return installProject;
532 public boolean assertProject(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, boolean installProject) throws PlatformException {
534 SubMonitor monitor = SubMonitor.convert(progressMonitor, 10);
536 final DatabaseManagement mgmt = new DatabaseManagement();
538 monitor.setTaskName("Asserting project resource exists in the database");
540 projectResource = session.syncRequest( Queries.resource( projectURI ) );
541 } catch (ResourceNotFoundException nfe) {
542 // Project was not found
543 if (workspacePolicy == RecoveryPolicy.ThrowError)
544 throw new PlatformException("Project Resource "+projectURI+" is not found in the database.");
545 // Create empty project with no features
547 Transaction.startTransaction(session, true);
549 // The project needs to be created mutable.
550 session.getService(XSupport.class).setServiceMode(true, false);
552 ArrayList<String> empty = new ArrayList<String>();
553 projectResource = mgmt.createProject(projectName, empty);
554 installProject |= true;
556 session.getService(XSupport.class).setServiceMode(false, false);
557 Transaction.commit();
559 Transaction.endTransaction();
561 //session.getService( LifecycleSupport.class ).save();
562 } catch (DatabaseException e) {
563 throw new PlatformException("Failed to create "+projectURI, e);
565 } catch (DatabaseException e) {
566 throw new PlatformException("Failed to create "+projectURI, e);
570 return installProject;
574 public void updateInstalledGroups(IProgressMonitor progressMonitor, boolean installProject) throws PlatformException {
578 // Attach all feature groups available in platform to created project
579 progressMonitor.setTaskName("Install all features");
580 Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
581 Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
586 Projects.setProjectInstalledGroups(graph, projectResource, groupsWithoutVersion));
587 } catch (DatabaseException ae) {
588 throw new PlatformException("Failed to install features", ae);
590 progressMonitor.worked(10);
595 public void assertSessionModel(IProgressMonitor progressMonitor) throws PlatformException {
597 Properties properties = session.getService(Properties.class);
598 final String clientId = properties.getProperty("clientId");
602 // Currently this needs to be done before data becomes available
603 VirtualGraphSupport support = session.getService(VirtualGraphSupport.class);
604 VirtualGraph activations = support.getWorkspacePersistent("activations");
606 Resource sessionModel = session.syncRequest(new Read<Resource>() {
609 public Resource perform(ReadGraph graph) throws DatabaseException {
611 Layer0X L0X = Layer0X.getInstance(graph);
612 for(Resource sessionModel : graph.syncRequest(new ObjectsWithType(graph.getRootLibrary(), L0X.HasSession, L0X.Session))) {
613 String id = graph.getPossibleRelatedValue(sessionModel, L0X.Session_HasClientId);
614 if(id != null && id.equals(clientId)) return sessionModel;
622 if(sessionModel == null) {
624 sessionModel = session.syncRequest(new WriteResultRequest<Resource>(activations) {
627 public Resource perform(WriteGraph graph) throws DatabaseException {
628 Layer0 L0 = Layer0.getInstance(graph);
629 Layer0X L0X = Layer0X.getInstance(graph);
630 Resource session = graph.newResource();
631 graph.claim(session, L0.InstanceOf, null, L0X.Session);
632 graph.claim(session, L0X.Session_HasUser, null, graph.getResource("http://Users/AdminUser"));
633 graph.addLiteral(session, L0X.Session_HasClientId, L0X.Session_HasClientId_Inverse, clientId, Bindings.STRING);
634 graph.claim(graph.getRootLibrary(), L0X.HasSession, session);
641 session.registerService(SessionModel.class, new PlatformSessionModel(sessionModel));
642 } catch (DatabaseException e) {
643 throw new PlatformException(e);
648 static class PlatformSessionModel implements SessionModel {
649 private final Resource sessionModel;
651 public PlatformSessionModel(Resource model) {
652 this.sessionModel = model;
656 public Resource getResource() {
661 public void resetDatabase(IProgressMonitor monitor) throws PlatformException {
662 File dbLocation = Platform.getLocation().append("db").toFile();
663 if(!dbLocation.exists()) return;
665 Driver driver = Manager.getDriver("procore");
666 Management management = driver.getManagement(dbLocation.getAbsolutePath(), null);
668 } catch (DatabaseException e) {
669 throw new PlatformException("Failed to remove database at " + dbLocation.getAbsolutePath(), e);
671 // We have created extra files to database folder which have to be deleted also.
672 // This is an awful idea! Do not create extra files to database folder!
674 for (int i=0; i<10; ++i) {
676 FileUtils.deleteAll(dbLocation);
679 } catch (IOException e) {
680 // Assuming this has been thrown because delete file/folder failed.
685 } catch (InterruptedException e) {
686 // Ignoring interrupted exception.
690 throw new PlatformException("Failed to remove database folder at " + dbLocation.getAbsolutePath(), t);
692 public void resetWorkspace(IProgressMonitor monitor, ArrayList<String> fileFilter) throws PlatformException, IllegalStateException, IOException {
693 File file = Platform.getLocation().toFile();
694 if (null != fileFilter)
695 FileUtils.deleteAllWithFilter(file , fileFilter);
696 resetDatabase(monitor);
699 public boolean handleBaselineDatabase() throws PlatformException {
700 Path workspaceLocation = Platform.getLocation().toFile().toPath();
701 Path baselineIndicatorFile = workspaceLocation.resolve(".baselined");
702 if (Files.isRegularFile(baselineIndicatorFile)) {
703 // This means that the workspace has already been initialized from
704 // a database baseline and further initialization is not necessary.
708 String dbBaselineArchive = System.getProperty("org.simantics.db.baseline", null);
709 if (dbBaselineArchive == null)
712 Path baseline = Paths.get(dbBaselineArchive);
713 if (!Files.isRegularFile(baseline))
714 throw new PlatformException("Specified database baseline archive " + baseline + " does not exist. Cannot initialize workspace database.");
716 DatabaseBaselines.validateBaselineFile(baseline);
717 DatabaseBaselines.validateWorkspaceForBaselineInitialization(workspaceLocation);
718 DatabaseBaselines.initializeWorkspaceWithBaseline(baseline, workspaceLocation, baselineIndicatorFile);
723 * Start-up the platform. The procedure consists of 8 steps. Once everything
724 * is up and running, all fields are set property.
727 * If workspacePolicy is FixErrors, there is an attempt to fix unexpected
728 * errors. It includes installing database files, installing ontologies, and
729 * installing project features.
732 * In Simantics Workbench this is handled in
733 * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
736 * If remote server is given, simantics plaform takes connection there
737 * instead of local server at "db/".
739 * @param workspacePolicy action to take on workspace/database related
741 * @param ontologyPolicy action to take on ontology mismatch
742 * @param progressMonitor optional progress monitor
743 * @param userAgent interface for resorting to user feedback during platform
744 * startup or <code>null</code> to resort to default measures
745 * @throws PlatformException
747 public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
748 OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
749 throws PlatformException
753 TimeLogger.log("Beginning of SimanticsPlatform.startUp");
755 LOGGER.info("Beginning of SimanticsPlatform.startUp");
757 SubMonitor monitor = SubMonitor.convert(progressMonitor, 1000);
759 // For debugging on what kind of platform automatic tests are running in
760 // case there are problems.
761 if ("true".equals(System.getProperty("org.simantics.dumpBundleState")))
762 dumpPlatformBundleState();
764 // 0. Consult all startup extensions before doing anything with the workspace.
765 StartupExtensions.consultStartupExtensions();
766 TimeLogger.log("Consulted platform pre-startup extensions");
768 // 0.1. Clear all temporary files
769 Simantics.clearTemporaryDirectory();
770 TimeLogger.log("Cleared temporary directory");
772 // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
773 VariableRepository.clear();
775 // 0.3 Handle baseline database before opening db
776 @SuppressWarnings("unused")
777 boolean usingBaseline = handleBaselineDatabase();
779 // 1. Assert there is a database at <workspace>/db
780 SessionDescriptor sessionDescriptor = setupDatabase(databaseDriverId, monitor.newChild(200, SubMonitor.SUPPRESS_NONE), workspacePolicy, userAgent);
781 session = sessionDescriptor.getSession();
782 TimeLogger.log("Database setup complete");
784 // 2. Delete all indexes if we cannot be certain they are up-to-date
785 // A full index rebuild will be done later, before project activation.
786 XSupport support = session.getService(XSupport.class);
787 if (support.rolledback()) {
789 DatabaseIndexing.deleteAllIndexes();
790 } catch (IOException e) {
791 throw new PlatformException(e);
795 // 3. Assert all graphs, and correct versions, are installed to the database
796 synchronizeOntologies(monitor.newChild(400, SubMonitor.SUPPRESS_NONE), ontologyPolicy, requireSynchronize);
797 TimeLogger.log("Synchronized ontologies");
799 // 4. Assert simantics.cfg exists
800 boolean installProject = assertConfiguration(monitor.newChild(25, SubMonitor.SUPPRESS_NONE),workspacePolicy);
802 // 5. Assert Project Resource is installed in the database
803 installProject = assertProject(monitor.newChild(25, SubMonitor.SUPPRESS_NONE), workspacePolicy, installProject);
805 // 6. Install all features into project, if in debug mode
806 updateInstalledGroups(monitor.newChild(25), true); //installProject);
807 TimeLogger.log("Installed all features into project");
809 // 7. Assert L0.Session in database for this session
810 assertSessionModel(monitor.newChild(25, SubMonitor.SUPPRESS_NONE));
812 session.getService(XSupport.class).setServiceMode(false, false);
815 monitor.setTaskName("Flush query cache");
816 session.syncRequest((Write) graph -> {
817 QueryControl qc = graph.getService(QueryControl.class);
820 TimeLogger.log("Flushed queries");
821 } catch (DatabaseException e) {
822 LOGGER.error("Flushing queries failed.", e);
824 boolean loadProject = true;
827 monitor.setTaskName("Open database session");
828 sessionContext = SimanticsPlatform.INSTANCE.createSessionContext(true);
829 // This must be before setSessionContext since some listeners might query this
830 sessionContext.setHint(SimanticsKeys.KEY_PROJECT, SimanticsPlatform.INSTANCE.projectResource);
832 Simantics.setSessionContext(sessionContext);
834 // 1. Put ResourceBinding that throws an exception to General Bindings
835 simanticsBindings = new SimanticsBindings( null );
836 Bindings.classBindingFactory.addFactory( simanticsBindings );
839 // 2. Create session-specific second Binding context (Databoard) and
840 // put that to Session as a service
841 Session session = sessionContext.getSession();
842 Databoard sessionDataboard = new Databoard();
843 session.registerService(Databoard.class, sessionDataboard);
844 simanticsBindings2 = new SimanticsBindings( session );
845 sessionDataboard.classBindingFactory.addFactory( simanticsBindings2 );
847 // Register datatype bindings
848 Bindings.defaultBindingFactory.getRepository().put(RGB.Integer.BINDING.type(), RGB.Integer.BINDING);
849 Bindings.defaultBindingFactory.getRepository().put(Font.BINDING.type(), Font.BINDING);
851 if (support.rolledback() || sessionDescriptor.isFreshDatabase()) {
852 monitor.setTaskName("Rebuilding all indexes");
854 session.getService(IndexedRelations.class).fullRebuild(monitor.newChild(100), session);
855 } catch (IndexException e) {
856 LOGGER.error("Failed to re-build all indexes", e);
863 TimeLogger.log("Load project");
864 monitor.setTaskName("Load project");
865 project = Projects.loadProject(sessionContext.getSession(), SimanticsPlatform.INSTANCE.projectResource);
866 sessionContext.setHint(ProjectKeys.KEY_PROJECT, project);
868 TimeLogger.log("Loading projects complete");
870 monitor.setTaskName("Activate project");
873 TimeLogger.log("Project activated");
876 } catch (DatabaseException e) {
877 LOGGER.error("Platform startup failed.", e);
878 throw new PlatformException(e);
879 } catch (ProjectException e) {
880 boolean hasStackTrace = e.getStackTrace().length > 0;
882 throw new PlatformException(e.getMessage(), hasStackTrace);
883 throw new PlatformException(e, hasStackTrace);
888 // #7650: improve shutdown robustness in all applications that use the platform
889 Runtime.getRuntime().addShutdownHook(shutdownHook);
891 // Discard database session undo history at this point to prevent
892 // the user from undoing any initialization operations performed
893 // by the platform startup.
894 SimanticsPlatform.INSTANCE.discardSessionUndoHistory();
895 TimeLogger.log("Discarded session undo history");
897 return sessionContext;
901 public SessionContext createSessionContext(boolean init) throws PlatformException {
903 // Construct and initialize SessionContext from Session.
904 SessionContext sessionContext = SessionContext.create(session, init);
905 TimeLogger.log("Session context created");
907 sessionContext.registerServices();
908 TimeLogger.log("Session services registered");
910 return sessionContext;
911 } catch (DatabaseException e) {
912 throw new PlatformException(e);
917 * Perform normal shutdown for the Simantics Platform.
919 * @param progressMonitor optional progress monitor
920 * @throws PlatformException
921 * @see {@link #shutdown(IProgressMonitor, boolean)}
923 public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
924 shutdown(progressMonitor, true);
928 * Shutdown Simantics Platform.
930 * In Simantics Workbench this is handled in
931 * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
933 * @param progressMonitor
934 * optional progress monitor
935 * @param clearTemporaryFiles
936 * allow or prevent deletion of temporary files at the end of the
938 * @throws PlatformException
940 public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
942 SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
943 PlatformException platformException = null;
945 progress.subTask("Close Project");
946 if (project != null) {
947 project.safeDispose();
952 progress.subTask("Close Database Session");
953 Databoard databoard = null;
954 if (sessionContext != null) {
955 Session s = sessionContext.peekSession();
957 databoard = s.peekService(Databoard.class);
959 progress.subTask("Flushing Index Caches");
961 Simantics.flushIndexCaches(progress.newChild(20), s);
962 } catch (Throwable t) {
963 LOGGER.error("Failed to flush index caches.", t);
967 progress.subTask("Close Database Session");
968 sessionContext.safeDispose();
969 sessionContext = null;
970 Simantics.setSessionContext(null);
972 if (simanticsBindings != null) {
973 Bindings.classBindingFactory.removeFactory( simanticsBindings );
974 simanticsBindings = null;
976 if (databoard != null) {
977 if (simanticsBindings2 != null) {
978 databoard.classBindingFactory.removeFactory( simanticsBindings2 );
979 simanticsBindings2 = null;
984 // Make sure Simantics clipboard doesn't store unwanted session data references.
985 Simantics.setClipboard(new SimanticsClipboardImpl());
990 projectResource = null;
991 currentDatabaseDriver = null;
993 DependenciesRelation.assertFinishedTracking();
995 } catch (Exception e) {
996 platformException = new PlatformException("Failed to shutdown Simantics Platform", e);
1000 progress.subTask("Shutting down database");
1002 if (null != databasebManagement)
1003 databasebManagement.shutdown();
1004 } catch (Throwable t) {
1005 LOGGER.error("Database shutdown failed.", t);
1007 progress.worked(10);
1009 if (clearTemporaryFiles) {
1010 progress.subTask("Clearing Workspace Temporary Directory");
1012 Simantics.clearTemporaryDirectory();
1013 } catch (Throwable t) {
1014 LOGGER.error("Failed to clear the temporary directory.", t);
1017 progress.worked(10);
1018 if (null != platformException)
1019 throw platformException;
1021 // #7650: improve shutdown robustness in all applications that use the platform
1022 Runtime.getRuntime().removeShutdownHook(shutdownHook);
1025 // TODO: consider removing this in the future ??
1027 public void stateChanged(LifecycleState newState) {
1028 if(newState == LifecycleState.CLOSED) {
1030 if(Platform.isRunning()) {
1031 mainThread.interrupt();
1038 * @return <code>true</code> if discard was successful, <code>false</code>
1039 * if there was no session, {@link UndoRedoSupport} or
1040 * {@link UndoContext} to discard through
1042 public boolean discardSessionUndoHistory() {
1043 Session s = session;
1045 UndoRedoSupport urs = s.peekService(UndoRedoSupport.class);
1047 UndoContext uc = urs.getUndoContext(s);
1057 public void reconnect(String databaseDriverId) throws Exception {
1058 // Starts database server.
1059 if (currentDatabaseDriver != null)
1060 databaseDriverId = currentDatabaseDriver;
1061 SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
1064 private void dumpPlatformBundleState() {
1065 BundleDescription[] bs = Platform.getPlatformAdmin().getState().getBundles();
1066 System.out.println("Total bundles: " + bs.length);
1067 for (BundleDescription b : bs) {
1068 System.out.format("%-80s @ %s\n", b.toString(), b.getLocation());
1072 private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
1073 throws InvalidFileFormatException, IOException
1075 File f = path.toFile();
1076 Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
1077 String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
1078 if (iniId == null) {
1079 dbIni.put("driver", "id", databaseDriverId);