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