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