]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics/src/org/simantics/SimanticsPlatform.java
Fixed multiple issues causing dangling references to discarded queries
[simantics/platform.git] / bundles / org.simantics / src / org / simantics / SimanticsPlatform.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2018 Association for Decentralized Information Management
3  * in Industry THTH ry.
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
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics;
13
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;
19
20 import java.io.File;
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Properties;
31 import java.util.Set;
32 import java.util.TreeMap;
33 import java.util.UUID;
34
35 import org.eclipse.core.runtime.ILog;
36 import org.eclipse.core.runtime.IProduct;
37 import org.eclipse.core.runtime.IProgressMonitor;
38 import org.eclipse.core.runtime.IStatus;
39 import org.eclipse.core.runtime.NullProgressMonitor;
40 import org.eclipse.core.runtime.Platform;
41 import org.eclipse.core.runtime.Status;
42 import org.eclipse.core.runtime.SubMonitor;
43 import org.eclipse.osgi.service.datalocation.Location;
44 import org.eclipse.osgi.service.resolver.BundleDescription;
45 import org.ini4j.Ini;
46 import org.ini4j.InvalidFileFormatException;
47 import org.simantics.SimanticsPlatform.OntologyRecoveryPolicy;
48 import org.simantics.SimanticsPlatform.RecoveryPolicy;
49 import org.simantics.databoard.Bindings;
50 import org.simantics.databoard.Databoard;
51 import org.simantics.datatypes.literal.Font;
52 import org.simantics.datatypes.literal.RGB;
53 import org.simantics.db.Driver;
54 import org.simantics.db.Driver.Management;
55 import org.simantics.db.Manager;
56 import org.simantics.db.ReadGraph;
57 import org.simantics.db.Resource;
58 import org.simantics.db.Session;
59 import org.simantics.db.SessionModel;
60 import org.simantics.db.UndoContext;
61 import org.simantics.db.VirtualGraph;
62 import org.simantics.db.WriteGraph;
63 import org.simantics.db.common.processor.MergingDelayedWriteProcessor;
64 import org.simantics.db.common.processor.MergingGraphRequestProcessor;
65 import org.simantics.db.common.request.ObjectsWithType;
66 import org.simantics.db.common.request.WriteResultRequest;
67 import org.simantics.db.common.utils.Transaction;
68 import org.simantics.db.exception.ClusterSetExistException;
69 import org.simantics.db.exception.DatabaseException;
70 import org.simantics.db.indexing.DatabaseIndexing;
71 import org.simantics.db.layer0.genericrelation.DependenciesRelation;
72 import org.simantics.db.layer0.genericrelation.IndexException;
73 import org.simantics.db.layer0.genericrelation.IndexedRelations;
74 import org.simantics.db.layer0.request.PossibleResource;
75 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
76 import org.simantics.db.layer0.util.SimanticsKeys;
77 import org.simantics.db.layer0.util.TGTransferableGraphSource;
78 import org.simantics.db.layer0.variable.VariableRepository;
79 import org.simantics.db.management.SessionContext;
80 import org.simantics.db.request.Read;
81 import org.simantics.db.request.Write;
82 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
83 import org.simantics.db.service.LifecycleSupport.LifecycleState;
84 import org.simantics.db.service.QueryControl;
85 import org.simantics.db.service.UndoRedoSupport;
86 import org.simantics.db.service.VirtualGraphSupport;
87 import org.simantics.db.service.XSupport;
88 import org.simantics.db.services.GlobalServiceInitializer;
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.TimedSessionCache;
99 import org.simantics.internal.startup.StartupExtensions;
100 import org.simantics.layer0.Layer0;
101 import org.simantics.operation.Layer0X;
102 import org.simantics.project.IProject;
103 import org.simantics.project.ProjectFeatures;
104 import org.simantics.project.ProjectKeys;
105 import org.simantics.project.Projects;
106 import org.simantics.project.SessionDescriptor;
107 import org.simantics.project.exception.ProjectException;
108 import org.simantics.project.features.registry.GroupReference;
109 import org.simantics.project.management.DatabaseManagement;
110 import org.simantics.project.management.GraphBundle;
111 import org.simantics.project.management.GraphBundleEx;
112 import org.simantics.project.management.GraphBundleRef;
113 import org.simantics.project.management.PlatformUtil;
114 import org.simantics.project.management.ServerManager;
115 import org.simantics.project.management.ServerManagerFactory;
116 import org.simantics.project.management.WorkspaceUtil;
117 import org.simantics.scl.compiler.module.repository.ModuleRepository;
118 import org.simantics.scl.osgi.SCLOsgi;
119 import org.simantics.utils.FileUtils;
120 import org.simantics.utils.datastructures.Pair;
121 import org.simantics.utils.strings.EString;
122 import org.simantics.utils.threads.ExecutorWorker;
123 import org.simantics.utils.threads.ThreadUtils;
124 import org.slf4j.Logger;
125 import org.slf4j.LoggerFactory;
126
127 /**
128  * SimanticsPlatform performs procedures required in order to get simantics
129  * workbench into operational state. This consists of the following steps:
130  * <ul>
131  *     <li> Asserting there is Database
132  *     </li>
133  *     <li> Starting Database process
134  *     </li>
135  *     <li> Opening a session to Database process
136  *     </li>
137  *     <li> Asserting required ontologies or other transferable graphs are installed in the database
138  *     </li>
139  *     <li> Asserting required project is installed in the database
140  *     </li>
141  *     <li> Asserting Simantics Features are installed in the database
142  *     </li>
143  *     <li> Asserting Simantics Features are installed to the project
144  *     </li>
145  *     <li> Shutdown: Save Session, Close session, Kill Database process
146  *     </li>
147  * </ul>
148  *
149  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
150  */
151 public class SimanticsPlatform implements LifecycleListener {
152
153     private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsPlatform.class);
154     
155     /**
156      * The policy is relevant when developing Simantics from Eclipse IDE.
157      * It is applied when the ontology in the database of a workspace doesn't match
158      * a newer ontology in the Eclipse workspace.
159      */
160     public static enum OntologyRecoveryPolicy { ThrowError, Merge, ReinstallDatabase, Bypass}
161
162     /**
163      * This policy dictates how the Simantics platform startup should react if
164      * the started workspace is not set up properly. The alternatives are to
165      * just throw an error and fail or to attempt all possible measures to fix
166      * the encountered problems.
167      */
168     public static enum RecoveryPolicy { ThrowError, FixError }
169
170     /** Singleton instance, started in SimanticsWorkbenchAdvisor */
171     public static final SimanticsPlatform INSTANCE = new SimanticsPlatform();
172
173     /** Set to true when the Simantics Platform is in good-and-go condition */
174     public boolean running;
175
176     /** ID of the database driver that the platform is currently using */
177     private String currentDatabaseDriver;
178
179     /** Database Session */
180     public Session session;
181     private Management databasebManagement;
182
183     /** Database session context */
184     public SessionContext sessionContext;
185
186     /** Project identifier in Database */
187     public String projectURI;
188
189     /** Project name */
190     public String projectName;
191
192     /** Project resource */
193     public Resource projectResource;
194
195     /** Session specific bindings */
196     public SimanticsBindings simanticsBindings;
197
198     public Thread mainThread;
199
200     private Thread shutdownHook = new Thread() {
201         @Override
202         public void run() {
203             try {
204                 LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
205                 shutdown(null, false);
206             } catch (PlatformException e) {
207                 LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
208                 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
209             }
210         }
211     };
212
213     /**
214      * The {@link IProject} activated by
215      * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
216      */
217     private IProject project;
218
219     protected ILog log;
220
221     /**
222      * Create a new simantics plaform manager in uninitialized state and
223      * with default policies. <p>
224      */
225     public SimanticsPlatform() {
226         log = Platform.getLog(Activator.getBundleContext().getBundle());
227         mainThread = Thread.currentThread();
228     }
229
230     public String getApplicationClientId() {
231         IProduct product = Platform.getProduct();
232         if(product == null) return "noProduct";//UUID.randomUUID().toString();
233         String application = product.getApplication();
234         return application != null ? application : UUID.randomUUID().toString();
235     }
236
237     private SessionDescriptor setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
238         if (progressMonitor == null)
239             progressMonitor = new NullProgressMonitor();
240         Path workspaceLocation = Platform.getLocation().toFile().toPath();
241         Path dbLocation = dbLocation();
242         Path dbIniPath = workspaceLocation.resolve("db.ini");
243         // The driver file overrides any command line arguments to prevent
244         // using the wrong driver for an existing database directory.
245         ServerManager serverManager;
246         try {
247             Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
248             databaseDriverId = dbIni.get("driver", "id");
249             serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
250         } catch (DatabaseException | IOException e) {
251             throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
252         }
253         progressMonitor.beginTask("Setting up Simantics Database", 100);
254         progressMonitor.setTaskName("Asserting Database is installed.");
255         String msg = "Failed to initialize Simantics database.";
256         try {
257             // Create database
258             log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
259             progressMonitor.setTaskName("Creating database at " + dbLocation);
260             databasebManagement = serverManager.getManagement(dbLocation.toFile());
261             databasebManagement.create();
262             currentDatabaseDriver = databaseDriverId;
263             // Create layer0.
264             return serverManager.createDatabase(dbLocation.toFile());
265         } catch (DatabaseException e) {
266             throw new PlatformException(msg, e);
267         } catch (Throwable e) {
268             throw new PlatformException(msg, e);
269         } finally {
270             progressMonitor.worked(20);
271         }
272     }
273
274     public void synchronizeOntologies(IProgressMonitor progressMonitor, OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize) throws PlatformException {
275
276         SubMonitor monitor = SubMonitor.convert(progressMonitor, 100);
277
278         monitor.setTaskName("Compile dynamic ontologies");
279         PlatformUtil.compileAllDynamicOntologies();
280
281         String message = "Asserting all ontologies are installed";
282         LOGGER.info(message);
283         monitor.setTaskName(message);
284
285         DatabaseManagement mgmt = new DatabaseManagement();
286         Map<GraphBundleRef, GraphBundleEx> platformTGs = new HashMap<>();
287         try {
288
289             // Get a list of bundles installed into the database
290             message = "find installed bundles from database";
291             monitor.subTask(message);
292             LOGGER.info(message);
293             Map<GraphBundleRef, GraphBundleEx> installedTGs = new HashMap<>();
294             for (GraphBundle b : session.syncRequest( mgmt.GraphBundleQuery )) {
295                 installedTGs.put(GraphBundleRef.of(b), GraphBundleEx.extend(b));
296             }
297
298             if(!requireSynchronize && installedTGs.size() > 1 && !Platform.inDevelopmentMode()) return;
299 //            if(installedTGs.size() > 1) return;
300
301             // Get a list of all bundles in the platform (Bundle Context)
302             message = "load all transferable graphs from platform";
303             monitor.subTask(message);
304             LOGGER.info(message);
305             Collection<GraphBundle> tgs = PlatformUtil.getAllGraphs();
306             message = "extend bundles to compile versions";
307             monitor.subTask(message);
308             LOGGER.info(message);
309             for (GraphBundle b : tgs) {
310                 GraphBundleEx gbe = GraphBundleEx.extend(b);
311                 gbe.build();
312                 platformTGs.put(GraphBundleRef.of(b), gbe);
313             }
314
315             // Compile a list of TGs that need to be installed or reinstalled in the database
316             message = "check bundle reinstallation demand";
317             monitor.subTask(message);
318             LOGGER.info(message);
319             List<GraphBundleEx> installTGs = new ArrayList<>();
320             // Create list of TGs to update, <newTg, oldTg>
321             Map<GraphBundleEx,GraphBundleEx> reinstallTGs = new TreeMap<>();
322             for (Entry<GraphBundleRef, GraphBundleEx> e : platformTGs.entrySet()) {
323                 GraphBundleRef key = e.getKey();
324                 GraphBundleEx platformBundle = e.getValue();
325                 GraphBundleEx existingBundle = installedTGs.get(key);
326                 
327 //                System.out.println("GraphBundleRef key=" + key.toString());
328                 
329                 if (existingBundle == null) {
330                     // Bundle did not exist in the database, put it into list of bundles to install
331                     installTGs.add(platformBundle);
332                 }
333                 else {
334                     // Bundle exists in the database
335                     boolean platformBundleIsNewer = existingBundle.getVersion().compareTo(platformBundle.getVersion())<0;
336                     if (!platformBundleIsNewer)
337                         continue;
338                     // Check hash of transferable graph to know whether to update or not.
339                     if (platformBundle.getHashcode() == existingBundle.getHashcode())
340                         continue;
341                     //System.out.println("Ontology hashcodes do not match: platform bundle="
342                     //        + platformBundle.getVersionedId() + ", hash=" + platformBundle.getHashcode()
343                     //        + "; existing bundle=" + existingBundle.getVersionedId() + ", hash=" + existingBundle.getHashcode());
344                     reinstallTGs.put(platformBundle, existingBundle);
345                 }
346             }
347             // INSTALL
348             // Database is missing graphs
349             if (!installTGs.isEmpty() || !reinstallTGs.isEmpty()) {
350                 session.getService(XSupport.class).setServiceMode(true, true);
351
352                 // Throw error
353                 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError || ontologyPolicy == OntologyRecoveryPolicy.Bypass) {
354                     StringBuilder sb = new StringBuilder("The following graphs are not installed in the database: ");
355                     if (!installTGs.isEmpty()) {
356                         int i = 0;
357                         for (GraphBundleEx e : installTGs) {
358                             if (i>0) sb.append(", ");
359                             i++;
360                             sb.append(e.toString());
361                         }
362                         sb.append(" is missing from the database.\n");
363                     }
364                     if (!reinstallTGs.isEmpty()) {
365                         int i = 0;
366                         for (Entry<GraphBundleEx, GraphBundleEx> e : reinstallTGs.entrySet()) {
367                             if (i>0) sb.append(", ");
368                             i++;
369                             sb.append(e.getKey().toString());
370                         }
371                         sb.append(" Database/Platform Bundle version mismatch.\n");
372                     }
373                     sb.append("Hint: Use -fixErrors to install the graphs.");
374                     if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError)
375                         throw new PlatformException(sb.toString());
376                     else
377                         log.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, sb.toString()));
378                 }
379                 
380                 // Reinstall database
381                 if (ontologyPolicy == OntologyRecoveryPolicy.ReinstallDatabase) {
382                     log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Reinstalling the database."));
383                     // TODO Install DB
384                     // Stop Session
385                     // Kill Process
386                     // Delete Database
387                     // Create Database
388                     // Start Database
389                     // Open Session
390                     // Install TGs
391                     throw new PlatformException("Reinstalling Database, NOT IMPLEMENTED");
392                 }
393
394                 boolean serviceModeEntered = false;
395
396                 if (ontologyPolicy == OntologyRecoveryPolicy.Merge) {
397                     message = "Merging ontology changes";
398                     monitor.subTask(message);
399                     LOGGER.info(message);
400                     // Sort missing TGs into install order
401                     GraphDependencyAnalyzer<GraphBundle> analyzer = new GraphDependencyAnalyzer<GraphBundle>();
402                     for(GraphBundle tg : installTGs) analyzer.addGraph(tg, tg.getGraph());
403                     for(GraphBundle tg : reinstallTGs.keySet()) analyzer.addGraph(tg, tg.getGraph());
404                     if(!analyzer.analyzeDependency()) {
405                         Collection<Pair<GraphBundle, GraphBundle>> problems = analyzer.getConflicts();
406                         StringBuilder sb = new StringBuilder();
407                         for (Pair<GraphBundle, GraphBundle> problem : problems) {
408                             sb.append("Conflict with "+problem.first+" and "+problem.second+".\n");
409                         }
410                         throw new PlatformException(sb.toString());
411                     } else if(!session.syncRequest( analyzer.queryExternalDependenciesSatisfied )) {
412                         Collection<IdentityNode> unsatisfiedDependencies = analyzer.getUnsatisfiedDependencies();
413                         StringBuilder sb = new StringBuilder();
414                         for (IdentityNode dep: unsatisfiedDependencies) {
415                             sb.append("Unsatisfied Dependency "+dep+". Required by\n");
416                             for(IU iu : GraphDependencyAnalyzer.toCollection(dep.getRequires())) {
417                                 sb.append("    " + ((GraphBundle)iu.getId()).getId() + "\n");
418                             }
419                         }
420                         throw new PlatformException(sb.toString());
421                     }
422
423                     message = "Analyzed graph bundles";
424                     monitor.subTask(message);
425                     LOGGER.info(message);
426
427                     List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
428                     if(!sortedBundles.isEmpty()) {
429
430                         session.syncRequest((Write) graph -> {
431                             try {
432                                 graph.newClusterSet(graph.getRootLibrary());
433                             } catch (ClusterSetExistException e) {
434                                 // Cluster set exist already, no problem.
435                             }
436                             graph.setClusterSet4NewResource(graph.getRootLibrary());
437                             graph.flushCluster();
438                         });
439
440                         boolean mergedOntologies = false;
441
442                         // Install TGs
443                         for(final GraphBundle tg : sortedBundles) {
444
445                                 final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
446                                 final GraphBundle oldTG = reinstallTGs.get(tg);
447
448                                 boolean createImmutable = tg.getImmutable();
449
450                                 if (oldTG==null) {
451
452                                 session.getService(XSupport.class).setServiceMode(true, createImmutable);
453
454                                 // Flush all queries once to allow even immutable request results to be invalidated
455                                 // because ontology installation affects immutable content and queries related to
456                                 // immutable content would not get invalidated at all otherwise.
457                                 if (!serviceModeEntered) {
458                                     serviceModeEntered = true;
459                                     session.getService(QueryControl.class).flush();
460                                 }
461
462                                         // Install TG
463                                         log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
464                                         ImportResult result = TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
465                                         if (!result.missingExternals.isEmpty()) {
466                                                 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Import of " + tg.toString() + " was missing the following external entities:\n" + EString.implode(result.missingExternals)));
467                                         }
468                                 } else {
469                                         if(!createImmutable)
470                                                 continue;
471
472                                         // Merge TG
473                                         startTransaction(session, false);
474                                         TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
475                                         final long[] oldResources = oldTG.getResourceArray();
476                                         boolean changes = TransferableGraphs.hasChanges(readGraph(), oldResources, delta);
477                                         endTransaction();
478                                         if (!changes) {
479                                             //log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Nothing to merge for "+tg.toString()));
480                                             continue;
481                                         }
482
483                                 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Merging new version of "+tg.toString()));
484
485                                         startTransaction(session, true);
486
487                                         //delta.print();
488                                         try {
489                                                 long[] resourceArray = TransferableGraphs.applyDelta(writeGraph(), oldResources, delta);
490                                                 tg.setResourceArray(resourceArray);
491                                                 mgmt.setGraphBundleEntry(tg);
492                                                 commit();
493                                                 mergedOntologies = true;
494                                         } catch (Throwable t) {
495                                                 throw new PlatformException(t);
496                                         } finally {
497                                                 endTransaction();
498                                         }
499                                 }
500                         }
501
502                         session.syncRequest((Write) graph -> {
503                             graph.setClusterSet4NewResource(graph.getRootLibrary());
504                             graph.flushCluster();
505                         });
506
507                         if (mergedOntologies)
508                             DatabaseIndexing.deleteAllIndexes();
509                     }
510                 }
511                 session.getService(XSupport.class).setServiceMode(false, false);
512                 if (serviceModeEntered) {
513                     // Flush all queries to ensure that queries that should now
514                     // be immutable are not left as mutable in the query cache.
515                     session.getService(QueryControl.class).flush();
516                 }
517             }
518             message = "Ontologies synchronized";
519             monitor.subTask(message);
520             LOGGER.info(message);
521             monitor.worked(100);
522         } catch (IOException e) {
523             throw new PlatformException(e);
524         } catch (DatabaseException e) {
525             throw new PlatformException(e);
526         }
527
528     }
529
530     public boolean assertConfiguration(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy) throws PlatformException {
531
532         if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
533
534         File workspaceLocation = Platform.getLocation().toFile();
535
536         boolean installProject = false;
537         progressMonitor.setTaskName("Asserting simantics.cfg is installed");
538         try {
539             File propertyFile = new File(workspaceLocation, "simantics.cfg");
540             Properties properties;
541             try {
542                 properties = WorkspaceUtil.readProperties(propertyFile);
543             } catch (IOException e) {
544                 if (workspacePolicy == RecoveryPolicy.ThrowError) throw new PlatformException("Could not load "+propertyFile);
545
546                 // Create a project and write Property file
547                 properties = new Properties();
548                 properties.setProperty("project_uri", "http://Projects/Development%20Project");
549                 properties.setProperty("project_name", "Development Project");
550                 WorkspaceUtil.writeProperties(propertyFile, properties);
551                 installProject |= true;
552             }
553             projectURI = properties.getProperty("project_uri");
554             projectName = properties.getProperty("project_name");
555             progressMonitor.worked(10);
556         } catch (IOException e) {
557             throw new PlatformException(e);
558         }
559
560         return installProject;
561
562     }
563
564     public boolean assertProject(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, boolean installProject) throws PlatformException {
565
566         SubMonitor monitor = SubMonitor.convert(progressMonitor, 10);
567
568         final DatabaseManagement mgmt = new DatabaseManagement();
569
570         monitor.setTaskName("Asserting project resource exists in the database");
571         try {
572             projectResource = session.syncRequest(new PossibleResource(projectURI));
573             if (projectResource == null) {
574                 // Project was not found
575                 if (workspacePolicy == RecoveryPolicy.ThrowError)
576                     throw new PlatformException("Project Resource "+projectURI+" is not found in the database.");
577                 // Create empty project with no features
578                 try {
579                     Transaction.startTransaction(session, true);
580                     try {
581                         // The project needs to be created mutable.
582                         session.getService(XSupport.class).setServiceMode(true, false);
583
584                         ArrayList<String> empty = new ArrayList<String>();
585                         projectResource = mgmt.createProject(projectName, empty);
586                         installProject |= true;
587
588                         session.getService(XSupport.class).setServiceMode(false, false);
589                         Transaction.commit();
590                     } finally {
591                         Transaction.endTransaction();
592                     }
593                     //session.getService( LifecycleSupport.class ).save();
594                 } catch (DatabaseException e) {
595                     throw new PlatformException("Failed to create "+projectURI, e);
596                 }
597             }
598         } catch (DatabaseException e) {
599             throw new PlatformException("Failed to create "+projectURI, e);
600         }
601         monitor.worked(10);
602
603         return installProject;
604
605     }
606
607     public void updateInstalledGroups(IProgressMonitor progressMonitor, boolean installProject) throws PlatformException {
608
609         if (installProject)
610         {
611             // Attach all feature groups available in platform to created project
612             progressMonitor.setTaskName("Install all features");
613             Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
614             Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
615
616             try {
617                 session.syncRequest(
618                         (Write) graph ->
619                         Projects.setProjectInstalledGroups(graph, projectResource, groupsWithoutVersion));
620             } catch (DatabaseException ae) {
621                 throw new PlatformException("Failed to install features", ae);
622             }
623             progressMonitor.worked(10);
624         }
625
626     }
627
628     public void assertSessionModel(IProgressMonitor progressMonitor) throws PlatformException {
629
630         Properties properties = session.getService(Properties.class);
631         final String clientId = properties.getProperty("clientId");
632
633         try {
634
635             // Currently this needs to be done before data becomes available
636             VirtualGraphSupport support = session.getService(VirtualGraphSupport.class);
637             VirtualGraph activations = support.getWorkspacePersistent("activations");
638
639             Resource sessionModel = session.syncRequest(new Read<Resource>() {
640
641                 @Override
642                 public Resource perform(ReadGraph graph) throws DatabaseException {
643
644                     Layer0X L0X = Layer0X.getInstance(graph);
645                     for(Resource sessionModel : graph.syncRequest(new ObjectsWithType(graph.getRootLibrary(), L0X.HasSession, L0X.Session))) {
646                         String id = graph.getPossibleRelatedValue(sessionModel, L0X.Session_HasClientId);
647                         if(id != null && id.equals(clientId)) return sessionModel;
648                     }
649                     return null;
650
651                 }
652
653             });
654
655             if(sessionModel == null) {
656
657                 sessionModel = session.syncRequest(new WriteResultRequest<Resource>(activations) {
658
659                     @Override
660                     public Resource perform(WriteGraph graph) throws DatabaseException {
661                         Layer0 L0 = Layer0.getInstance(graph);
662                         Layer0X L0X = Layer0X.getInstance(graph);
663                         Resource session = graph.newResource();
664                         graph.claim(session, L0.InstanceOf, null, L0X.Session);
665                         graph.claim(session, L0X.Session_HasUser, null, graph.getResource("http://Users/AdminUser"));
666                         graph.addLiteral(session, L0X.Session_HasClientId, L0X.Session_HasClientId_Inverse, clientId, Bindings.STRING);
667                         graph.claim(graph.getRootLibrary(), L0X.HasSession, session);
668                         return session;
669                     }
670                 });
671
672             }
673
674             session.registerService(SessionModel.class, new PlatformSessionModel(sessionModel));
675         } catch (DatabaseException e) {
676             throw new PlatformException(e);
677         }
678
679     }
680
681     static class PlatformSessionModel implements SessionModel {
682         private final Resource sessionModel;
683
684         public PlatformSessionModel(Resource model) {
685             this.sessionModel = model;
686         }
687
688         @Override
689         public Resource getResource() {
690             return sessionModel;
691         }
692     }
693
694     public void resetDatabase(IProgressMonitor monitor) throws PlatformException {
695         // TODO: fix this to use Path APIs
696         File dbLocation = dbLocation().toFile();
697         if(!dbLocation.exists())
698             return;
699         try { // Load driver
700             Driver driver = Manager.getDriver("acorn");
701             Management management = driver.getManagement(dbLocation.getAbsolutePath(), null);
702             management.delete();
703         } catch (DatabaseException e) {
704             throw new PlatformException("Failed to remove database at " + dbLocation.getAbsolutePath(), e);
705         }
706         // We have created extra files to database folder which have to be deleted also.
707         // This is an awful idea! Do not create extra files to database folder!
708         Throwable t = null;
709         for (int i=0; i<10; ++i) {
710             try {
711                 FileUtils.deleteAll(dbLocation);
712                 t = null;
713                 break;
714             } catch (IOException e) {
715                 // Assuming this has been thrown because delete file/folder failed.
716                 t = e;
717             }
718             try {
719                 Thread.sleep(200);
720             } catch (InterruptedException e) {
721                 // Ignoring interrupted exception.
722             }
723         }
724         if (null != t)
725             throw new PlatformException("Failed to remove database folder at " + dbLocation.getAbsolutePath(), t);
726     }
727     public void resetWorkspace(IProgressMonitor monitor, ArrayList<String> fileFilter) throws PlatformException, IllegalStateException, IOException {
728         File file = Platform.getLocation().toFile();
729         if (null != fileFilter)
730             FileUtils.deleteAllWithFilter(file , fileFilter);
731         resetDatabase(monitor);
732     }
733
734     private static Path tryGetInstallLocation() {
735         Location l = Platform.getInstallLocation();
736         return l == null ? null : new File(l.getURL().getPath()).toPath();
737     }
738
739     public Path databaseExists() {
740         Path dbLocation = dbLocation();
741         if(Files.exists(dbLocation))
742             return dbLocation;
743         return null;
744     }
745     
746     public Path dbLocation() {
747         Path workspaceLocation = Platform.getLocation().toFile().toPath();
748         return workspaceLocation.resolve("db");
749     }
750
751     /**
752      * Start-up the platform. The procedure consists of 8 steps. Once everything
753      * is up and running, all fields are set property.
754      * <p>
755      *
756      * If workspacePolicy is FixErrors, there is an attempt to fix unexpected
757      * errors. It includes installing database files, installing ontologies, and
758      * installing project features.
759      * <p>
760      *
761      * In Simantics Workbench this is handled in
762      * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
763      * <p>
764      *
765      * If remote server is given, simantics plaform takes connection there
766      * instead of local server at "db/".
767      *
768      * @param workspacePolicy action to take on workspace/database related
769      *        errors
770      * @param ontologyPolicy action to take on ontology mismatch
771      * @param progressMonitor optional progress monitor
772      * @param userAgent interface for resorting to user feedback during platform
773      *        startup or <code>null</code> to resort to default measures
774      * @throws PlatformException
775      */
776     public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
777             OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
778     throws PlatformException
779     {
780
781         assert(!running);
782         LOGGER.info("Beginning of SimanticsPlatform.startUp");
783
784         SubMonitor monitor = SubMonitor.convert(progressMonitor, 1000);
785
786         // For debugging on what kind of platform automatic tests are running in
787         // case there are problems.
788         if ("true".equals(System.getProperty("org.simantics.dumpBundleState")))
789             dumpPlatformBundleState();
790
791         // 0. Consult all startup extensions before doing anything with the workspace.
792         StartupExtensions.consultStartupExtensions();
793         LOGGER.info("Consulted platform pre-startup extensions");
794
795         // 0.1. Clear all temporary files
796         Simantics.clearTemporaryDirectory();
797         LOGGER.info("Cleared temporary directory");
798
799         // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
800         VariableRepository.clear();
801
802         // 0.2.1 Activate org.simantics.scl.osgi to prime the SCL compiler early.
803         @SuppressWarnings("unused")
804         ModuleRepository modRepo = SCLOsgi.MODULE_REPOSITORY;
805
806         // 0.3 Handle baseline database before opening db
807         @SuppressWarnings("unused")
808         boolean usingBaseline = DatabaseBaselines.handleBaselineDatabase(tryGetInstallLocation(), databaseExists() != null);
809
810         // 1. Assert there is a database at <workspace>/db
811         SessionDescriptor sessionDescriptor = setupDatabase(databaseDriverId, monitor.newChild(200, SubMonitor.SUPPRESS_NONE), workspacePolicy, userAgent);
812         session = sessionDescriptor.getSession();
813         LOGGER.info("Database setup complete");
814
815         // 2. Delete all indexes if we cannot be certain they are up-to-date
816         //    A full index rebuild will be done later, before project activation.
817         XSupport support = session.getService(XSupport.class);
818         if (support.rolledback()) {
819             try {
820                 DatabaseIndexing.deleteAllIndexes();
821             } catch (IOException e) {
822                 throw new PlatformException(e);
823             }
824         }
825
826         // 3. Assert all graphs, and correct versions, are installed to the database
827         synchronizeOntologies(monitor.newChild(400, SubMonitor.SUPPRESS_NONE), ontologyPolicy, requireSynchronize);
828
829         // 4. Assert simantics.cfg exists
830         boolean installProject = assertConfiguration(monitor.newChild(25, SubMonitor.SUPPRESS_NONE),workspacePolicy);
831
832         // 5. Assert Project Resource is installed in the database
833         installProject = assertProject(monitor.newChild(25, SubMonitor.SUPPRESS_NONE), workspacePolicy, installProject);
834
835         // 6. Install all features into project, if in debug mode
836         updateInstalledGroups(monitor.newChild(25), true); //installProject);
837         LOGGER.info("Installed all features into project");
838
839         // 7. Assert L0.Session in database for this session
840         assertSessionModel(monitor.newChild(25, SubMonitor.SUPPRESS_NONE));
841
842         session.getService(XSupport.class).setServiceMode(false, false);
843
844         try {
845             String message = "Flush query cache";
846             monitor.setTaskName(message);
847             LOGGER.info(message);
848             session.syncRequest((Write) graph -> {
849                 QueryControl qc = graph.getService(QueryControl.class);
850                 qc.flush(graph);
851             });
852         } catch (DatabaseException e) {
853             LOGGER.error("Flushing queries failed.", e);
854         }
855         boolean loadProject = true;
856         try {
857             String message = "Open database session";
858             monitor.setTaskName(message);
859             LOGGER.info(message);
860                 sessionContext = SimanticsPlatform.INSTANCE.createSessionContext(true);
861                 // This must be before setSessionContext since some listeners might query this
862             sessionContext.setHint(SimanticsKeys.KEY_PROJECT, SimanticsPlatform.INSTANCE.projectResource);
863
864             Simantics.setSessionContext(sessionContext);
865
866             // 1. Put ResourceBinding that throws an exception to General Bindings
867             message = "Put ResourceBinding that throws an exception to General Bindings";
868             LOGGER.info(message);
869             simanticsBindings = new SimanticsBindings();
870             Bindings.classBindingFactory.addFactory( simanticsBindings );
871
872             Session session = sessionContext.getSession();
873             session.registerService(Databoard.class, Bindings.databoard);
874
875             // Register datatype bindings
876             message = "Register datatype bindings";
877             LOGGER.info(message);
878             Bindings.defaultBindingFactory.getRepository().put(RGB.Integer.BINDING.type(), RGB.Integer.BINDING);
879             Bindings.defaultBindingFactory.getRepository().put(Font.BINDING.type(), Font.BINDING);
880
881             if (support.rolledback() || sessionDescriptor.isFreshDatabase()) {
882                 message = "Rebuilding all indexes";
883                 LOGGER.info(message);
884                 monitor.setTaskName(message);
885                 try {
886                     session.getService(IndexedRelations.class).fullRebuild(monitor.newChild(100), session);
887                 } catch (IndexException e) {
888                     LOGGER.error("Failed to re-build all indexes", e);
889                 }
890             } else {
891                 monitor.worked(100);
892             }
893
894             if(loadProject) {
895                 message = "Load project";
896                 monitor.setTaskName(message);
897                 LOGGER.info(message);
898                 project = Projects.loadProject(sessionContext.getSession(), SimanticsPlatform.INSTANCE.projectResource);
899                 sessionContext.setHint(ProjectKeys.KEY_PROJECT, project);
900                 monitor.worked(100);
901                 message = "Loading projects complete";
902                 LOGGER.info(message);
903
904                 message = "Activate project";
905                 monitor.setTaskName(message);
906                 LOGGER.info(message);
907                 project.activate();
908                 monitor.worked(100);
909                 LOGGER.info("Project activated");
910             }
911
912         } catch (DatabaseException e) {
913             LOGGER.error("Platform startup failed.", e);
914             throw new PlatformException(e);
915         } catch (ProjectException e) {
916             boolean hasStackTrace = e.getStackTrace().length > 0;
917             if (!hasStackTrace)
918                 throw new PlatformException(e.getMessage(), hasStackTrace);
919             throw new PlatformException(e, hasStackTrace);
920         }
921
922         running = true;
923
924         // #7650: improve shutdown robustness in all applications that use the platform
925         Runtime.getRuntime().addShutdownHook(shutdownHook);
926
927         // Discard database session undo history at this point to prevent
928         // the user from undoing any initialization operations performed
929         // by the platform startup.
930         SimanticsPlatform.INSTANCE.discardSessionUndoHistory();
931         LOGGER.info("Discarded session undo history");
932
933         return sessionContext;
934
935     }
936
937     public void registerServices(SessionContext context) {
938         new GlobalServiceInitializer().initialize(session);
939         session.registerService(MergingGraphRequestProcessor.class, new MergingGraphRequestProcessor("SessionService", session, 20));
940         session.registerService(MergingDelayedWriteProcessor.class, new MergingDelayedWriteProcessor(session, 20));
941     }
942
943
944     public SessionContext createSessionContext(boolean init) throws PlatformException {
945         try {
946             // Construct and initialize SessionContext from Session.
947             SessionContext sessionContext = SessionContext.create(session, init);
948             String message = "Session context created";
949             LOGGER.info(message);
950             if (init) {
951                 registerServices(sessionContext);
952                 message = "Session services registered";
953                 LOGGER.info(message);
954             }
955             return sessionContext;
956         } catch (DatabaseException e) {
957             throw new PlatformException(e);
958         }
959     }
960
961     /**
962      * Perform normal shutdown for the Simantics Platform.
963      *
964      * @param progressMonitor optional progress monitor
965      * @throws PlatformException
966      * @see {@link #shutdown(IProgressMonitor, boolean)}
967      */
968     public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
969         shutdown(progressMonitor, true);
970     }
971
972     /**
973      * Shutdown Simantics Platform.
974      *
975      * In Simantics Workbench this is handled in
976      * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
977      *
978      * @param progressMonitor
979      *            optional progress monitor
980      * @param clearTemporaryFiles
981      *            allow or prevent deletion of temporary files at the end of the
982      *            shutdown procedure
983      * @throws PlatformException
984      */
985     public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
986     {
987         SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
988         PlatformException platformException = null;
989         try {
990             progress.subTask("Close Project");
991             if (project != null) {
992                 project.safeDispose();
993             }
994             progress.worked(10);
995
996             // NOP at the moment
997             TimedSessionCache.close();
998
999             progress.subTask("Thread pools");
1000             ThreadUtils.shutdown();
1001             ExecutorWorker.shutdown();
1002             progress.worked(5);
1003
1004             running = false;
1005             progress.subTask("Close Database Session");
1006             if (sessionContext != null) {
1007                 Session s = sessionContext.peekSession();
1008                 if (s != null) {
1009                     progress.subTask("Flushing Index Caches");
1010                     try {
1011                         Simantics.flushIndexCaches(progress.newChild(20), s);
1012                     } catch (Throwable t) {
1013                         LOGGER.error("Failed to flush index caches.", t);
1014                     }
1015
1016                     if("true".equals(System.getProperty("org.simantics.db.persistQueries"))) {
1017                         progress.subTask("Saving Queries");
1018                         Simantics.saveQueries(s);
1019                     }
1020                 }
1021
1022                 progress.subTask("Close Database Session");
1023                 sessionContext.safeDispose();
1024                 sessionContext = null;
1025                 Simantics.setSessionContext(null);
1026             }
1027             if (simanticsBindings != null) {
1028                 Bindings.classBindingFactory.removeFactory( simanticsBindings );
1029                 simanticsBindings = null;
1030             }
1031
1032             // Make sure Simantics clipboard doesn't store unwanted session data references.
1033             Simantics.setClipboard(new SimanticsClipboardImpl());
1034
1035             progress.worked(50);
1036
1037             session = null;
1038             projectResource = null;
1039             currentDatabaseDriver = null;
1040
1041             DependenciesRelation.assertFinishedTracking();
1042
1043         } catch (Exception e) {
1044             platformException = new PlatformException("Failed to shutdown Simantics Platform", e);
1045         }
1046
1047         progress.worked(10);
1048         progress.subTask("Shutting down database");
1049         try {
1050             if (null != databasebManagement)
1051                 databasebManagement.shutdown();
1052         } catch (Throwable t) {
1053             LOGGER.error("Database shutdown failed.", t);
1054         }
1055         progress.worked(10);
1056
1057         progress.subTask("Clear index status");
1058         try {
1059             // Everything ok, clear index dirty state.
1060             DatabaseIndexing.clearAllDirty();
1061         } catch (IOException e) {
1062             LOGGER.error("Problems encountered while refreshing database index states, see exception for details.", e);
1063         }
1064         progress.worked(5);
1065
1066         if (clearTemporaryFiles) {
1067             progress.subTask("Clearing Workspace Temporary Directory");
1068             try {
1069                 Simantics.clearTemporaryDirectory();
1070             } catch (Throwable t) {
1071                 LOGGER.error("Failed to clear the temporary directory.", t);
1072             }
1073         }
1074         progress.worked(10);
1075         if (null != platformException)
1076             throw platformException;
1077
1078         // #7650: improve shutdown robustness in all applications that use the platform
1079         Runtime.getRuntime().removeShutdownHook(shutdownHook);
1080     }
1081
1082     // TODO: consider removing this in the future ??
1083     @Override
1084     public void stateChanged(LifecycleState newState) {
1085         if(newState == LifecycleState.CLOSED) {
1086             if(running) {
1087                 if(Platform.isRunning()) {
1088                     mainThread.interrupt();
1089                 }
1090             }
1091         }
1092     }
1093
1094     /**
1095      * @return <code>true</code> if discard was successful, <code>false</code>
1096      *         if there was no session, {@link UndoRedoSupport} or
1097      *         {@link UndoContext} to discard through
1098      */
1099     public boolean discardSessionUndoHistory() {
1100         Session s = session;
1101         if (s != null) {
1102             UndoRedoSupport urs = s.peekService(UndoRedoSupport.class);
1103             if (urs != null) {
1104                 UndoContext uc = urs.getUndoContext(s);
1105                 if (uc != null) {
1106                     uc.clear();
1107                     return true;
1108                 }
1109             }
1110         }
1111         return false;
1112     }
1113
1114     public void reconnect(String databaseDriverId) throws Exception {
1115         // Starts database server.
1116         if (currentDatabaseDriver != null)
1117             databaseDriverId = currentDatabaseDriver;
1118         SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
1119     }
1120
1121     private void dumpPlatformBundleState() {
1122         BundleDescription[] bs = Platform.getPlatformAdmin().getState().getBundles();
1123         System.out.println("Total bundles: " + bs.length);
1124         for (BundleDescription b : bs) {
1125             System.out.format("%-80s @ %s\n", b.toString(), b.getLocation());
1126         }
1127     }
1128
1129     private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
1130             throws InvalidFileFormatException, IOException
1131     {
1132         File f = path.toFile();
1133         Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
1134         String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
1135         if (iniId == null) {
1136             dbIni.put("driver", "id", databaseDriverId);
1137             dbIni.store(f);
1138         }
1139         return dbIni;
1140     }
1141
1142 }