Platform startup now reads the used database driver from <ws>/db.ini
[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.util.ArrayList;
25 import java.util.Collection;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 import java.util.Map.Entry;
30 import java.util.Properties;
31 import java.util.Set;
32 import java.util.TreeMap;
33 import java.util.UUID;
34
35 import org.eclipse.core.runtime.ILog;
36 import org.eclipse.core.runtime.IProduct;
37 import org.eclipse.core.runtime.IProgressMonitor;
38 import org.eclipse.core.runtime.IStatus;
39 import org.eclipse.core.runtime.NullProgressMonitor;
40 import org.eclipse.core.runtime.Platform;
41 import org.eclipse.core.runtime.Status;
42 import org.eclipse.core.runtime.SubMonitor;
43 import org.eclipse.osgi.service.resolver.BundleDescription;
44 import org.ini4j.Ini;
45 import org.ini4j.InvalidFileFormatException;
46 import org.simantics.databoard.Bindings;
47 import org.simantics.databoard.Databoard;
48 import org.simantics.datatypes.literal.Font;
49 import org.simantics.datatypes.literal.RGB;
50 import org.simantics.db.Driver;
51 import org.simantics.db.Driver.Management;
52 import org.simantics.db.Manager;
53 import org.simantics.db.ReadGraph;
54 import org.simantics.db.Resource;
55 import org.simantics.db.Session;
56 import org.simantics.db.SessionModel;
57 import org.simantics.db.UndoContext;
58 import org.simantics.db.VirtualGraph;
59 import org.simantics.db.WriteGraph;
60 import org.simantics.db.common.request.ObjectsWithType;
61 import org.simantics.db.common.request.Queries;
62 import org.simantics.db.common.request.WriteRequest;
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.exception.ResourceNotFoundException;
68 import org.simantics.db.indexing.DatabaseIndexing;
69 import org.simantics.db.layer0.genericrelation.DependenciesRelation;
70 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
71 import org.simantics.db.layer0.util.SimanticsKeys;
72 import org.simantics.db.layer0.util.TGTransferableGraphSource;
73 import org.simantics.db.layer0.variable.VariableRepository;
74 import org.simantics.db.management.SessionContext;
75 import org.simantics.db.request.Read;
76 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
77 import org.simantics.db.service.LifecycleSupport.LifecycleState;
78 import org.simantics.db.service.QueryControl;
79 import org.simantics.db.service.UndoRedoSupport;
80 import org.simantics.db.service.VirtualGraphSupport;
81 import org.simantics.db.service.XSupport;
82 import org.simantics.graph.db.GraphDependencyAnalyzer;
83 import org.simantics.graph.db.GraphDependencyAnalyzer.IU;
84 import org.simantics.graph.db.GraphDependencyAnalyzer.IdentityNode;
85 import org.simantics.graph.db.IImportAdvisor;
86 import org.simantics.graph.db.TransferableGraphs;
87 import org.simantics.graph.diff.Diff;
88 import org.simantics.graph.diff.TransferableGraphDelta1;
89 import org.simantics.internal.Activator;
90 import org.simantics.internal.startup.StartupExtensions;
91 import org.simantics.layer0.Layer0;
92 import org.simantics.operation.Layer0X;
93 import org.simantics.project.IProject;
94 import org.simantics.project.ProjectFeatures;
95 import org.simantics.project.ProjectKeys;
96 import org.simantics.project.Projects;
97 import org.simantics.project.exception.ProjectException;
98 import org.simantics.project.features.registry.GroupReference;
99 import org.simantics.project.management.DatabaseManagement;
100 import org.simantics.project.management.GraphBundle;
101 import org.simantics.project.management.GraphBundleEx;
102 import org.simantics.project.management.GraphBundleRef;
103 import org.simantics.project.management.PlatformUtil;
104 import org.simantics.project.management.ServerManager;
105 import org.simantics.project.management.ServerManagerFactory;
106 import org.simantics.project.management.WorkspaceUtil;
107 import org.simantics.utils.FileUtils;
108 import org.simantics.utils.datastructures.Pair;
109 import org.simantics.utils.logging.TimeLogger;
110 import org.slf4j.Logger;
111 import org.slf4j.LoggerFactory;
112
113 /**
114  * SimanticsPlatform performs procedures required in order to get simantics
115  * workbench into operational state. This consists of the following steps:
116  * <ul>
117  *     <li> Asserting there is Database
118  *     </li>
119  *     <li> Starting Database process
120  *     </li>
121  *     <li> Opening a session to Database process
122  *     </li>
123  *     <li> Asserting required ontologies or other transferable graphs are installed in the database
124  *     </li>
125  *     <li> Asserting required project is installed in the database
126  *     </li>
127  *     <li> Asserting Simantics Features are installed in the database
128  *     </li>
129  *     <li> Asserting Simantics Features are installed to the project
130  *     </li>
131  *     <li> Shutdown: Save Session, Close session, Kill Database process
132  *     </li>
133  * </ul>
134  *
135  * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
136  */
137 public class SimanticsPlatform implements LifecycleListener {
138
139     private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsPlatform.class);
140     
141     /**
142      * The policy is relevant when developing Simantics from Eclipse IDE.
143      * It is applied when the ontology in the database of a workspace doesn't match
144      * a newer ontology in the Eclipse workspace.
145      */
146     public static enum OntologyRecoveryPolicy { ThrowError, Merge, ReinstallDatabase }
147
148     /**
149      * This policy dictates how the Simantics platform startup should react if
150      * the started workspace is not set up properly. The alternatives are to
151      * just throw an error and fail or to attempt all possible measures to fix
152      * the encountered problems.
153      */
154     public static enum RecoveryPolicy { ThrowError, FixError }
155
156     /** Singleton instance, started in SimanticsWorkbenchAdvisor */
157     public static final SimanticsPlatform INSTANCE = new SimanticsPlatform();
158
159     /** Set to true when the Simantics Platform is in good-and-go condition */
160     public boolean running;
161
162     /** ID of the database driver that the platform is currently using */
163     private String currentDatabaseDriver;
164
165     /** Database Session */
166     public Session session;
167     private Management databasebManagement;
168
169     /** Database session context */
170     public SessionContext sessionContext;
171
172     /** Project identifier in Database */
173     public String projectURI;
174
175     /** Project name */
176     public String projectName;
177
178     /** Project resource */
179     public Resource projectResource;
180
181     /** Session specific bindings */
182     public SimanticsBindings simanticsBindings;
183     public SimanticsBindings simanticsBindings2;
184
185     public Thread mainThread;
186
187     /**
188      * The {@link IProject} activated by
189      * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
190      */
191     private IProject project;
192
193     protected ILog log;
194
195     /**
196      * Create a new simantics plaform manager in uninitialized state and
197      * with default policies. <p>
198      */
199     public SimanticsPlatform() {
200         log = Platform.getLog(Activator.getBundleContext().getBundle());
201         mainThread = Thread.currentThread();
202     }
203
204     public String getApplicationClientId() {
205         IProduct product = Platform.getProduct();
206         if(product == null) return "noProduct";//UUID.randomUUID().toString();
207         String application = product.getApplication();
208         return application != null ? application : UUID.randomUUID().toString();
209     }
210
211     private Session setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
212         if (progressMonitor == null)
213             progressMonitor = new NullProgressMonitor();
214         Path workspaceLocation = Platform.getLocation().toFile().toPath();
215         Path dbLocation = workspaceLocation.resolve("db");
216         Path dbIniPath = workspaceLocation.resolve("db.ini");
217         // The driver file overrides any command line arguments to prevent
218         // using the wrong driver for an existing database directory.
219         ServerManager serverManager;
220         try {
221             Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
222             databaseDriverId = dbIni.get("driver", "id");
223             serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
224         } catch (DatabaseException | IOException e) {
225             throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
226         }
227         progressMonitor.beginTask("Setting up Simantics Database", 100);
228         progressMonitor.setTaskName("Asserting Database is installed.");
229         String msg = "Failed to initialize Simantics database.";
230         try {
231             // Create database
232             log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
233             progressMonitor.setTaskName("Creating database at " + dbLocation);
234             databasebManagement = serverManager.getManagement(dbLocation.toFile());
235             databasebManagement.create();
236             currentDatabaseDriver = databaseDriverId;
237             // Create layer0.
238             return serverManager.createDatabase(dbLocation.toFile());
239         } catch (DatabaseException e) {
240             throw new PlatformException(msg, e);
241         } catch (Throwable e) {
242             throw new PlatformException(msg, e);
243         } finally {
244             progressMonitor.worked(20);
245         }
246     }
247
248     public void synchronizeOntologies(IProgressMonitor progressMonitor, OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize) throws PlatformException {
249
250         if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
251
252         final DatabaseManagement mgmt = new DatabaseManagement();
253
254         PlatformUtil.compileAllDynamicOntologies();
255
256         progressMonitor.setTaskName("Asserting all ontologies are installed");
257         final Map<GraphBundleRef, GraphBundleEx> platformTGs = new HashMap<GraphBundleRef, GraphBundleEx>();
258         try {
259
260             // Get a list of bundles installed into the database
261             progressMonitor.subTask("find installed bundles from database");
262             Map<GraphBundleRef, GraphBundleEx> installedTGs = new HashMap<GraphBundleRef, GraphBundleEx>();
263             for (GraphBundle b : session.syncRequest( mgmt.GraphBundleQuery )) {
264                 installedTGs.put(GraphBundleRef.of(b), GraphBundleEx.extend(b));
265             }
266
267             if(!requireSynchronize && installedTGs.size() > 1 && !Platform.inDevelopmentMode()) return;
268 //            if(installedTGs.size() > 1) return;
269
270             // Get a list of all bundles in the platform (Bundle Context)
271             List<GraphBundle> tgs = new ArrayList<GraphBundle>();
272             progressMonitor.subTask("load all transferable graphs from platform");
273             PlatformUtil.getAllGraphs(tgs);
274             progressMonitor.subTask("extend bundles to compile versions");
275             for (GraphBundle b : tgs) {
276                 GraphBundleEx gbe = GraphBundleEx.extend(b);
277                 gbe.build();
278                 platformTGs.put(GraphBundleRef.of(b), gbe);
279             }
280
281             // Compile a list of TGs that need to be installed or reinstalled in the database
282             progressMonitor.subTask("check bundle reinstallation demand");
283             List<GraphBundleEx> installTGs = new ArrayList<GraphBundleEx>();
284             // Create list of TGs to update, <newTg, oldTg>
285             Map<GraphBundleEx,GraphBundleEx> reinstallTGs = new TreeMap<GraphBundleEx,GraphBundleEx>();
286             for (Entry<GraphBundleRef, GraphBundleEx> e : platformTGs.entrySet()) {
287                 GraphBundleRef key = e.getKey();
288                 GraphBundleEx platformBundle = e.getValue();
289                 GraphBundleEx existingBundle = installedTGs.get(key);
290                 
291 //                System.out.println("GraphBundleRef key=" + key.toString());
292                 
293                 if (existingBundle == null) {
294                     // Bundle did not exist in the database, put it into list of bundles to install
295                     installTGs.add(platformBundle);
296                 }
297                 else {
298                     // Bundle exists in the database
299                     boolean platformBundleIsNewer = existingBundle.getVersion().compareTo(platformBundle.getVersion())<0;
300                     if (!platformBundleIsNewer)
301                         continue;
302                     // Check hash of transferable graph to know whether to update or not.
303                     if (platformBundle.getHashcode() == existingBundle.getHashcode())
304                         continue;
305                     //System.out.println("Ontology hashcodes do not match: platform bundle="
306                     //        + platformBundle.getVersionedId() + ", hash=" + platformBundle.getHashcode()
307                     //        + "; existing bundle=" + existingBundle.getVersionedId() + ", hash=" + existingBundle.getHashcode());
308                     reinstallTGs.put(platformBundle, existingBundle);
309                 }
310             }
311             // INSTALL
312             // Database is missing graphs
313             if (!installTGs.isEmpty() || !reinstallTGs.isEmpty()) {
314                 session.getService(XSupport.class).setServiceMode(true, true);
315
316                 // Throw error
317                 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError) {
318                     StringBuilder sb = new StringBuilder("The following graphs are not installed in the database: ");
319                     if (!installTGs.isEmpty()) {
320                         int i = 0;
321                         for (GraphBundleEx e : installTGs) {
322                             if (i>0) sb.append(", ");
323                             i++;
324                             sb.append(e.toString());
325                         }
326                         sb.append(" is missing from the database.\n");
327                     }
328                     if (!reinstallTGs.isEmpty()) {
329                         int i = 0;
330                         for (Entry<GraphBundleEx, GraphBundleEx> e : reinstallTGs.entrySet()) {
331                             if (i>0) sb.append(", ");
332                             i++;
333                             sb.append(e.getKey().toString());
334                         }
335                         sb.append(" Database/Platform Bundle version mismatch.\n");
336                     }
337                     sb.append("Hint: Use -fixErrors to install the graphs.");
338                     throw new PlatformException(sb.toString());
339                 }
340                 // Reinstall database
341                 if (ontologyPolicy == OntologyRecoveryPolicy.ReinstallDatabase) {
342                     log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Reinstalling the database."));
343                     // TODO Install DB
344                     // Stop Session
345                     // Kill Process
346                     // Delete Database
347                     // Create Database
348                     // Start Database
349                     // Open Session
350                     // Install TGs
351                     throw new PlatformException("Reinstalling Database, NOT IMPLEMENTED");
352                 }
353
354                 if (ontologyPolicy == OntologyRecoveryPolicy.Merge) {
355                     progressMonitor.subTask("Merging ontology changes");
356                     // Sort missing TGs into install order
357                     GraphDependencyAnalyzer<GraphBundle> analyzer = new GraphDependencyAnalyzer<GraphBundle>();
358                     for(GraphBundle tg : installTGs) analyzer.addGraph(tg, tg.getGraph());
359                     for(GraphBundle tg : reinstallTGs.keySet()) analyzer.addGraph(tg, tg.getGraph());
360                     if(!analyzer.analyzeDependency()) {
361                         Collection<Pair<GraphBundle, GraphBundle>> problems = analyzer.getConflicts();
362                         StringBuilder sb = new StringBuilder();
363                         for (Pair<GraphBundle, GraphBundle> problem : problems) {
364                             sb.append("Conflict with "+problem.first+" and "+problem.second+".\n");
365                         }
366                         throw new PlatformException(sb.toString());
367                     }
368                     else if(!session.syncRequest( analyzer.queryExternalDependenciesSatisfied )) {
369                         Collection<IdentityNode> unsatisfiedDependencies = analyzer.getUnsatisfiedDependencies();
370                         StringBuilder sb = new StringBuilder();
371                         for (IdentityNode dep: unsatisfiedDependencies) {
372                             sb.append("Unsatisfied Dependency "+dep+". Required by\n");
373                             for(IU iu : GraphDependencyAnalyzer.toCollection(dep.getRequires())) {
374                                 sb.append("    " + ((GraphBundle)iu.getId()).getId() + "\n");
375                             }
376                         }
377                         throw new PlatformException(sb.toString());
378                     }
379                     
380                     List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
381                     if(!sortedBundles.isEmpty()) {
382                         
383                         session.syncRequest(new WriteRequest() {
384                             @Override
385                             public void perform(WriteGraph graph) throws DatabaseException {
386                                 try {
387                                     graph.newClusterSet(graph.getRootLibrary());
388                                 } catch (ClusterSetExistException e) {
389                                     // Cluster set exist already, no problem.
390                                 }
391                                 graph.setClusterSet4NewResource(graph.getRootLibrary());
392                                 graph.flushCluster();
393                             }
394                         });
395
396                         boolean mergedOntologies = false;
397
398                         // Install TGs
399                         for(final GraphBundle tg : sortedBundles) {
400
401                                 final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
402                                 final GraphBundle oldTG = reinstallTGs.get(tg);
403
404                                 if (oldTG==null) {
405                                         
406                                         boolean createImmutable = tg.getImmutable();
407                                 session.getService(XSupport.class).setServiceMode(true, createImmutable);
408
409                                         // Install TG
410                                         log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
411                                         TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
412                                 } else {
413                                         // Merge TG
414                                         startTransaction(session, false);
415                                         TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
416                                         final long[] oldResources = oldTG.getResourceArray();
417                                         boolean changes = TransferableGraphs.hasChanges(readGraph(), oldResources, delta);
418                                         endTransaction();
419                                         if (!changes) {
420                                             //log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Nothing to merge for "+tg.toString()));
421                                             continue;
422                                         }
423
424                                 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Merging new version of "+tg.toString()));
425
426                                         startTransaction(session, true);
427                                         
428                                         //delta.print();
429                                         try {
430                                                 
431                                                 
432                                                 long[] resourceArray = TransferableGraphs.applyDelta(writeGraph(), oldResources, delta);
433                                                 tg.setResourceArray(resourceArray);
434                                                 mgmt.setGraphBundleEntry(tg);
435                                                 commit();
436                                                 mergedOntologies = true;
437                                         } catch (Throwable t) {
438                                                 throw new PlatformException(t);
439                                         } finally {
440                                                 endTransaction();
441                                         }
442                                 }
443                         }
444                         
445                         session.syncRequest(new WriteRequest() {
446                             @Override
447                             public void perform(WriteGraph graph) throws DatabaseException {
448                                 graph.setClusterSet4NewResource(graph.getRootLibrary());
449                                 graph.flushCluster();
450                             }
451                         });
452
453                         if (mergedOntologies)
454                             DatabaseIndexing.deleteAllIndexes();
455                     }
456
457                     TimeLogger.log("Ontologies synchronized.");
458                     
459                 }
460                 session.getService(XSupport.class).setServiceMode(false, false);
461             }
462             progressMonitor.worked(20);
463         } catch (IOException e) {
464             throw new PlatformException(e);
465         } catch (DatabaseException e) {
466             throw new PlatformException(e);
467         }
468
469     }
470
471     public boolean assertConfiguration(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy) throws PlatformException {
472
473         if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
474
475         File workspaceLocation = Platform.getLocation().toFile();
476
477         boolean installProject = false;
478         progressMonitor.setTaskName("Asserting simantics.cfg is installed");
479         try {
480             File propertyFile = new File(workspaceLocation, "simantics.cfg");
481             Properties properties;
482             try {
483                 properties = WorkspaceUtil.readProperties(propertyFile);
484             } catch (IOException e) {
485                 if (workspacePolicy == RecoveryPolicy.ThrowError) throw new PlatformException("Could not load "+propertyFile);
486
487                 // Create a project and write Property file
488                 properties = new Properties();
489                 properties.setProperty("project_uri", "http://Projects/Development%20Project");
490                 properties.setProperty("project_name", "Development Project");
491                 WorkspaceUtil.writeProperties(propertyFile, properties);
492                 installProject |= true;
493             }
494             projectURI = properties.getProperty("project_uri");
495             projectName = properties.getProperty("project_name");
496             progressMonitor.worked(10);
497         } catch (IOException e) {
498             throw new PlatformException(e);
499         }
500
501         return installProject;
502
503     }
504
505     public boolean assertProject(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, boolean installProject) throws PlatformException {
506
507         if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
508
509         final DatabaseManagement mgmt = new DatabaseManagement();
510
511         progressMonitor.setTaskName("Asserting project resource exists in the database");
512         try {
513             projectResource = session.syncRequest( Queries.resource( projectURI ) );
514         } catch (ResourceNotFoundException nfe) {
515             // Project was not found
516             if (workspacePolicy == RecoveryPolicy.ThrowError)
517                 throw new PlatformException("Project Resource "+projectURI+" is not found in the database.");
518             // Create empty project with no features
519             try {
520                 Transaction.startTransaction(session, true);
521                 try {
522                     // The project needs to be created mutable.
523                     session.getService(XSupport.class).setServiceMode(true, false);
524
525                     ArrayList<String> empty = new ArrayList<String>();
526                     projectResource = mgmt.createProject(projectName, empty);
527                     installProject |= true;
528
529                     session.getService(XSupport.class).setServiceMode(false, false);
530                     Transaction.commit();
531                 } finally {
532                     Transaction.endTransaction();
533                 }
534                 //session.getService( LifecycleSupport.class ).save();
535             } catch (DatabaseException e) {
536                 throw new PlatformException("Failed to create "+projectURI, e);
537             }
538         } catch (DatabaseException e) {
539             throw new PlatformException("Failed to create "+projectURI, e);
540         }
541         progressMonitor.worked(10);
542
543         return installProject;
544
545     }
546
547     public void updateInstalledGroups(IProgressMonitor progressMonitor, boolean installProject) throws PlatformException {
548
549         if (installProject)
550         {
551             // Attach all feature groups available in platform to created project
552             progressMonitor.setTaskName("Install all features");
553             Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
554             Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
555
556     //        final List<String> Platform_Features = new ArrayList<String>();
557     //
558     //        // Convert graph instances
559     //        Collection<TransferableGraph1> platformGraphs = new ArrayList<TransferableGraph1>();
560     //        for (GraphBundleEx e : platformTGs.values()) platformGraphs.add( e.getGraph() );
561     //        IGraph graph = Graphs.createGraph(platformGraphs);
562     //
563     //        Res    PublishedProjectFeatures = UriUtils.uriToPath( ProjectResource.URIs.PublishedProjectFeatures );
564     //        Path   HasFeature = UriUtils.uriToPath( ProjectResource.URIs.HasFeature );
565     //        for(Res feature : graph.getObjects(PublishedProjectFeatures, HasFeature)) {
566     //            System.out.println("Installing Project Feature: "+feature.toString());
567     //            Platform_Features.add( feature.toString() );
568     //        }
569
570             try {
571                 Transaction.startTransaction(session, true);
572                 try {
573     //                for (String feature : Platform_Features) {
574     //                    try {
575     //                        getResource(feature);
576     //                    } catch(ResourceNotFoundException e) {
577     //                        System.out.println(feature+" not found");
578     //                    }
579     //                    mgmt.installFeature(projectResource, feature);
580     //                }
581                     Projects.setProjectInstalledGroups(writeGraph(), projectResource, groupsWithoutVersion);
582                     Transaction.commit();
583                 } finally {
584                     Transaction.endTransaction();
585                 }
586                 //session.getService( LifecycleSupport.class ).save();
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     /**
700      * Start-up the platform. The procedure consists of 8 steps. Once everything
701      * is up and running, all fields are set property.
702      * <p>
703      *
704      * If workspacePolicy is FixErrors, there is an attempt to fix unexpected
705      * errors. It includes installing database files, installing ontologies, and
706      * installing project features.
707      * <p>
708      *
709      * In SWB this is handled in SimanticsWorkbenchAdvisor#openWindows().
710      * <p>
711      *
712      * If remote server is given, simantics plaform takes connection there
713      * instead of local server at "db/".
714      *
715      * @param workspacePolicy action to take on workspace/database related
716      *        errors
717      * @param ontologyPolicy action to take on ontology mismatch
718      * @param progressMonitor optional progress monitor
719      * @param userAgent interface for resorting to user feedback during platform
720      *        startup or <code>null</code> to resort to default measures
721      * @throws PlatformException
722      */
723     public SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
724             OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
725     throws PlatformException
726     {
727
728         assert(!running);
729         TimeLogger.log("Beginning of SimanticsPlatform.startUp");
730
731         LOGGER.info("Beginning of SimanticsPlatform.startUp");
732         
733         if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
734
735         // For debugging on what kind of platform automatic tests are running in
736         // case there are problems.
737         if ("true".equals(System.getProperty("org.simantics.dumpBundleState")))
738             dumpPlatformBundleState();
739
740         // 0. Consult all startup extensions before doing anything with the workspace.
741         StartupExtensions.consultStartupExtensions();
742         TimeLogger.log("Consulted platform pre-startup extensions");
743
744         // 0.1. Clear all temporary files
745         Simantics.clearTemporaryDirectory();
746         TimeLogger.log("Cleared temporary directory");
747
748         // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
749         VariableRepository.clear();
750         
751         // 1. Assert there is a database at <workspace>/db
752         session = setupDatabase(databaseDriverId, progressMonitor, workspacePolicy, userAgent);
753         TimeLogger.log("Database setup complete");
754         
755         // 1.1 
756         XSupport support = session.getService(XSupport.class);
757         if (support.rolledback()) {
758             try {
759                 DatabaseIndexing.deleteAllIndexes();
760             } catch (IOException e) {
761                 throw new PlatformException(e);
762             }
763         }
764         
765         // 2. Assert all graphs, and correct versions, are installed to the database
766         synchronizeOntologies(progressMonitor, ontologyPolicy, requireSynchronize);
767         TimeLogger.log("Synchronized ontologies");
768
769         // 4. Assert simantics.cfg exists
770         boolean installProject = assertConfiguration(progressMonitor,workspacePolicy);
771
772         // 5. Assert Project Resource is installed in the database
773         installProject = assertProject(progressMonitor, workspacePolicy, installProject);
774
775         // 6. Install all features into project, if in debug mode
776         updateInstalledGroups(progressMonitor, installProject);
777         TimeLogger.log("Installed all features into project");
778
779         // 7. Assert L0.Session in database for this session
780         assertSessionModel(progressMonitor);
781
782         session.getService(XSupport.class).setServiceMode(false, false);
783
784         try {
785             session.sync(new WriteRequest() {
786
787                 @Override
788                 public void perform(WriteGraph graph) throws DatabaseException {
789                     QueryControl qc = graph.getService(QueryControl.class);
790                     qc.flush(graph);
791                 }
792
793             });
794             TimeLogger.log("Flushed queries");
795         } catch (DatabaseException e) {
796             LOGGER.error("Flushing queries failed.", e);
797         }
798         boolean loadProject = true;
799         try {
800
801                 sessionContext = SimanticsPlatform.INSTANCE.createSessionContext(true);
802                 // This must be before setSessionContext since some listeners might query this
803             sessionContext.setHint(SimanticsKeys.KEY_PROJECT, SimanticsPlatform.INSTANCE.projectResource);
804
805             Simantics.setSessionContext(sessionContext);
806
807             // 1. Put ResourceBinding that throws an exception to General Bindings
808             simanticsBindings = new SimanticsBindings( null );
809             Bindings.classBindingFactory.addFactory( simanticsBindings );
810
811
812             // 2. Create session-specific second Binding context (Databoard) and
813             //    put that to Session as a service
814             Session session = sessionContext.getSession();
815             Databoard sessionDataboard = new Databoard();
816             session.registerService(Databoard.class, sessionDataboard);
817             simanticsBindings2 = new SimanticsBindings( session );
818             sessionDataboard.classBindingFactory.addFactory( simanticsBindings2 );
819
820             // Register datatype bindings
821             Bindings.defaultBindingFactory.getRepository().put(RGB.Integer.BINDING.type(), RGB.Integer.BINDING);
822             Bindings.defaultBindingFactory.getRepository().put(Font.BINDING.type(), Font.BINDING);
823
824             if(loadProject) {
825
826                 TimeLogger.log("Load projects");
827                 project = Projects.loadProject(sessionContext.getSession(), SimanticsPlatform.INSTANCE.projectResource);
828
829                 sessionContext.setHint(ProjectKeys.KEY_PROJECT, project);
830                 TimeLogger.log("Loading projects complete");
831
832                 project.activate();
833                 TimeLogger.log("Project activated");
834             }
835
836         } catch (DatabaseException e) {
837             LOGGER.error("Platform startup failed.", e);
838             throw new PlatformException(e);
839         } catch (ProjectException e) {
840             boolean hasStackTrace = e.getStackTrace().length > 0;
841             if (!hasStackTrace)
842                 throw new PlatformException(e.getMessage(), hasStackTrace);
843             throw new PlatformException(e, hasStackTrace);
844         }
845
846         running = true;
847
848         return sessionContext;
849
850     }
851
852     public SessionContext createSessionContext(boolean init) throws PlatformException {
853         try {
854             // Construct and initialize SessionContext from Session.
855             SessionContext sessionContext = SessionContext.create(session, init);
856             if (init)
857                 sessionContext.registerServices();
858             return sessionContext;
859         } catch (DatabaseException e) {
860             throw new PlatformException(e);
861         }
862     }
863
864 //    private static File getIgnorePrerequisitesFile(URL workspaceUrl) {
865 //        if (workspaceUrl == null)
866 //            return null;
867 //        return new File(workspaceUrl.getPath(), ".ignorePrerequisites");
868 //    }
869 //
870 //    private void ensurePrerequisites(IProgressMonitor progressMonitor, PlatformUserAgent userAgent) throws PlatformException {
871 //        Location loc = Platform.getInstanceLocation();
872 //        File ignorePrerequisites = getIgnorePrerequisitesFile(loc.getURL());
873 //        if (loc.isSet() && ignorePrerequisites != null) {
874 //            if (ignorePrerequisites.exists() || ignorePrerequisites.isFile())
875 //                return;
876 //        }
877 //
878 //        try {
879 //            ServerEnvironment.ensureServerDependenciesMet();
880 //        } catch (ExecutionEnvironmentException e) {
881 //            // Not installed properly, ask user whether to try installation.
882 //            try {
883 //                StringBuilder msg = new StringBuilder();
884 //                msg.append("Your system seems to be missing the following prerequisites for running this application:\n\n");
885 //                for (Product product : e.requiredProducts)
886 //                    msg.append("\t" + product.getDescription() + "\n");
887 //                msg.append("\nYou can either install the missing components now or ignore and attempt to start the application without them. Ignore Always will ignore this question for this workspace.");
888 //                msg.append("\n\nSelecting Cancel will close the application.");
889 //
890 //                int selection = 0;
891 //                if (userAgent != null) {
892 //                    selection = userAgent.showPrompt("Missing Prerequisites", msg.toString(), new String[] {
893 //                        "Install Pre-requisites",
894 //                        "Ignore Now",
895 //                        "Ignore Always",
896 //                        "Cancel"
897 //                    }, selection);
898 //                }
899 //                boolean tryInstall = false;
900 //                switch (selection) {
901 //                    case 0:
902 //                        tryInstall = true;
903 //                        break;
904 //                    case 2:
905 //                        ignorePrerequisites.createNewFile();
906 //                    case 1:
907 //                        break;
908 //                    case 3:
909 //                    case -1:
910 //                        throw new CancelStartupException();
911 //                }
912 //
913 //                if (tryInstall) {
914 //                    // Try to install it and check for success afterwards.
915 //                    ServerEnvironment.tryInstallDependencies(progressMonitor);
916 //                    ServerEnvironment.ensureServerDependenciesMet();
917 //                }
918 //            } catch (InstallException ie) {
919 //                throw new PlatformException(ie);
920 //            } catch (ExecutionEnvironmentException eee) {
921 //                throw new PlatformException(eee);
922 //            } catch (IOException ie) {
923 //                throw new PlatformException(ie);
924 //            }
925 //        }
926 //    }
927
928     /**
929      * Shutdown Simantics Platform.
930      *
931      * In SWB this is handled in SimanticsWorkbenchAdvisor#disconnectFromWorkspace.
932      *
933      * @param progressMonitor optional progress monitor
934      * @throws PlatformException
935      */
936     public void shutdown(IProgressMonitor progressMonitor) throws PlatformException
937     {
938         SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
939         PlatformException platformException = null;
940         try {
941             progress.subTask("Close Project");
942             if (project != null) {
943                 project.safeDispose();
944             }
945             progress.worked(10);
946
947             running = false;
948             progress.subTask("Close Database Session");
949             Databoard databoard = null;
950             if (sessionContext != null) {
951                 Session s = sessionContext.peekSession();
952                 if (s != null) {
953                     databoard = s.peekService(Databoard.class);
954
955                     progress.subTask("Flushing Index Caches");
956                     try {
957                         Simantics.flushIndexCaches(progress.newChild(20), s);
958                     } catch (Throwable t) {
959                         LOGGER.error("Failed to flush index caches.", t);
960                     }
961                 }
962
963                 progress.subTask("Close Database Session");
964                 sessionContext.safeDispose();
965                 sessionContext = null;
966                 Simantics.setSessionContext(null);
967             }
968             if (simanticsBindings != null) {
969                 Bindings.classBindingFactory.removeFactory( simanticsBindings );
970                 simanticsBindings = null;
971             }
972             if (databoard != null) {
973                 if (simanticsBindings2 != null) {
974                         databoard.classBindingFactory.removeFactory( simanticsBindings2 );
975                         simanticsBindings2 = null;
976                 }
977                 databoard.clear();
978             }
979
980             // Make sure Simantics clipboard doesn't store unwanted session data references.
981             Simantics.setClipboard(new SimanticsClipboardImpl());
982
983             progress.worked(30);
984
985             session = null;
986             projectResource = null;
987             currentDatabaseDriver = null;
988
989             DependenciesRelation.assertFinishedTracking();
990
991         } catch (Exception e) {
992             platformException = new PlatformException("Failed to shutdown Simantics Platform", e);
993         }
994
995         progress.worked(10);
996         progress.subTask("Shutting down database");
997         try {
998             if (null != databasebManagement)
999                 databasebManagement.shutdown();
1000         } catch (Throwable t) {
1001             LOGGER.error("Database shutdown failed.", t);
1002         }
1003         progress.worked(10);
1004
1005         progress.subTask("Clearing Workspace Temporary Directory");
1006         try {
1007             Simantics.clearTemporaryDirectory();
1008         } catch (Throwable t) {
1009             LOGGER.error("Failed to clear the temporary directory.", t);
1010         }
1011         progress.worked(10);
1012         if (null != platformException)
1013             throw platformException;
1014     }
1015
1016     // TODO: consider removing this in the future ??
1017     @Override
1018     public void stateChanged(LifecycleState newState) {
1019         if(newState == LifecycleState.CLOSED) {
1020             if(running) {
1021                 if(Platform.isRunning()) {
1022                     mainThread.interrupt();
1023                 }
1024             }
1025         }
1026     }
1027
1028     /**
1029      * @return <code>true</code> if discard was successful, <code>false</code>
1030      *         if there was no session, {@link UndoRedoSupport} or
1031      *         {@link UndoContext} to discard through
1032      */
1033     public boolean discardSessionUndoHistory() {
1034         Session s = session;
1035         if (s != null) {
1036             UndoRedoSupport urs = s.peekService(UndoRedoSupport.class);
1037             if (urs != null) {
1038                 UndoContext uc = urs.getUndoContext(s);
1039                 if (uc != null) {
1040                     uc.clear();
1041                     return true;
1042                 }
1043             }
1044         }
1045         return false;
1046     }
1047
1048     public void reconnect(String databaseDriverId) throws Exception {
1049         // Starts database server.
1050         if (currentDatabaseDriver != null)
1051             databaseDriverId = currentDatabaseDriver;
1052         SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
1053     }
1054
1055     private void dumpPlatformBundleState() {
1056         BundleDescription[] bs = Platform.getPlatformAdmin().getState().getBundles();
1057         System.out.println("Total bundles: " + bs.length);
1058         for (BundleDescription b : bs) {
1059             System.out.format("%-80s @ %s\n", b.toString(), b.getLocation());
1060         }
1061     }
1062
1063     private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
1064             throws InvalidFileFormatException, IOException
1065     {
1066         File f = path.toFile();
1067         Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
1068         String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
1069         if (iniId == null) {
1070             dbIni.put("driver", "id", databaseDriverId);
1071             dbIni.store(f);
1072         }
1073         return dbIni;
1074     }
1075
1076 }