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