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