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