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