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