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