1 /*******************************************************************************
2 * Copyright (c) 2007, 2018 Association for Decentralized Information Management
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
10 * VTT Technical Research Centre of Finland - initial API and implementation
11 *******************************************************************************/
12 package org.simantics;
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;
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;
29 import java.util.Map.Entry;
30 import java.util.Properties;
32 import java.util.TreeMap;
33 import java.util.UUID;
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.datalocation.Location;
44 import org.eclipse.osgi.service.resolver.BundleDescription;
46 import org.ini4j.InvalidFileFormatException;
47 import org.simantics.SimanticsPlatform.OntologyRecoveryPolicy;
48 import org.simantics.SimanticsPlatform.RecoveryPolicy;
49 import org.simantics.databoard.Bindings;
50 import org.simantics.databoard.Databoard;
51 import org.simantics.datatypes.literal.Font;
52 import org.simantics.datatypes.literal.RGB;
53 import org.simantics.db.Driver;
54 import org.simantics.db.Driver.Management;
55 import org.simantics.db.Manager;
56 import org.simantics.db.ReadGraph;
57 import org.simantics.db.Resource;
58 import org.simantics.db.Session;
59 import org.simantics.db.SessionModel;
60 import org.simantics.db.UndoContext;
61 import org.simantics.db.VirtualGraph;
62 import org.simantics.db.WriteGraph;
63 import org.simantics.db.common.processor.MergingDelayedWriteProcessor;
64 import org.simantics.db.common.processor.MergingGraphRequestProcessor;
65 import org.simantics.db.common.request.ObjectsWithType;
66 import org.simantics.db.common.request.WriteResultRequest;
67 import org.simantics.db.common.utils.Transaction;
68 import org.simantics.db.exception.ClusterSetExistException;
69 import org.simantics.db.exception.DatabaseException;
70 import org.simantics.db.indexing.DatabaseIndexing;
71 import org.simantics.db.layer0.genericrelation.DependenciesRelation;
72 import org.simantics.db.layer0.genericrelation.IndexException;
73 import org.simantics.db.layer0.genericrelation.IndexedRelations;
74 import org.simantics.db.layer0.request.PossibleResource;
75 import org.simantics.db.layer0.util.SimanticsClipboardImpl;
76 import org.simantics.db.layer0.util.SimanticsKeys;
77 import org.simantics.db.layer0.util.TGTransferableGraphSource;
78 import org.simantics.db.layer0.variable.VariableRepository;
79 import org.simantics.db.management.SessionContext;
80 import org.simantics.db.request.Read;
81 import org.simantics.db.request.Write;
82 import org.simantics.db.service.LifecycleSupport.LifecycleListener;
83 import org.simantics.db.service.LifecycleSupport.LifecycleState;
84 import org.simantics.db.service.QueryControl;
85 import org.simantics.db.service.UndoRedoSupport;
86 import org.simantics.db.service.VirtualGraphSupport;
87 import org.simantics.db.service.XSupport;
88 import org.simantics.db.services.GlobalServiceInitializer;
89 import org.simantics.graph.db.GraphDependencyAnalyzer;
90 import org.simantics.graph.db.GraphDependencyAnalyzer.IU;
91 import org.simantics.graph.db.GraphDependencyAnalyzer.IdentityNode;
92 import org.simantics.graph.db.IImportAdvisor;
93 import org.simantics.graph.db.ImportResult;
94 import org.simantics.graph.db.TransferableGraphs;
95 import org.simantics.graph.diff.Diff;
96 import org.simantics.graph.diff.TransferableGraphDelta1;
97 import org.simantics.internal.Activator;
98 import org.simantics.internal.TimedSessionCache;
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.SessionDescriptor;
107 import org.simantics.project.exception.ProjectException;
108 import org.simantics.project.features.registry.GroupReference;
109 import org.simantics.project.management.DatabaseManagement;
110 import org.simantics.project.management.GraphBundle;
111 import org.simantics.project.management.GraphBundleEx;
112 import org.simantics.project.management.GraphBundleRef;
113 import org.simantics.project.management.PlatformUtil;
114 import org.simantics.project.management.ServerManager;
115 import org.simantics.project.management.ServerManagerFactory;
116 import org.simantics.project.management.WorkspaceUtil;
117 import org.simantics.scl.compiler.module.repository.ModuleRepository;
118 import org.simantics.scl.osgi.SCLOsgi;
119 import org.simantics.utils.FileUtils;
120 import org.simantics.utils.datastructures.Pair;
121 import org.simantics.utils.strings.EString;
122 import org.simantics.utils.threads.ExecutorWorker;
123 import org.simantics.utils.threads.ThreadUtils;
124 import org.slf4j.Logger;
125 import org.slf4j.LoggerFactory;
128 * SimanticsPlatform performs procedures required in order to get simantics
129 * workbench into operational state. This consists of the following steps:
131 * <li> Asserting there is Database
133 * <li> Starting Database process
135 * <li> Opening a session to Database process
137 * <li> Asserting required ontologies or other transferable graphs are installed in the database
139 * <li> Asserting required project is installed in the database
141 * <li> Asserting Simantics Features are installed in the database
143 * <li> Asserting Simantics Features are installed to the project
145 * <li> Shutdown: Save Session, Close session, Kill Database process
149 * @author Toni Kalajainen <toni.kalajainen@vtt.fi>
151 public class SimanticsPlatform implements LifecycleListener {
153 private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsPlatform.class);
156 * The policy is relevant when developing Simantics from Eclipse IDE.
157 * It is applied when the ontology in the database of a workspace doesn't match
158 * a newer ontology in the Eclipse workspace.
160 public static enum OntologyRecoveryPolicy { ThrowError, Merge, ReinstallDatabase, Bypass}
163 * This policy dictates how the Simantics platform startup should react if
164 * the started workspace is not set up properly. The alternatives are to
165 * just throw an error and fail or to attempt all possible measures to fix
166 * the encountered problems.
168 public static enum RecoveryPolicy { ThrowError, FixError }
170 /** Singleton instance, started in SimanticsWorkbenchAdvisor */
171 public static final SimanticsPlatform INSTANCE = new SimanticsPlatform();
173 /** Set to true when the Simantics Platform is in good-and-go condition */
174 public boolean running;
176 /** ID of the database driver that the platform is currently using */
177 private String currentDatabaseDriver;
179 /** Database Session */
180 public Session session;
181 private Management databasebManagement;
183 /** Database session context */
184 public SessionContext sessionContext;
186 /** Project identifier in Database */
187 public String projectURI;
190 public String projectName;
192 /** Project resource */
193 public Resource projectResource;
195 /** Session specific bindings */
196 public SimanticsBindings simanticsBindings;
198 public Thread mainThread;
200 private Thread shutdownHook = new Thread() {
204 LOGGER.warn("Simantics platform was not properly shut down. Executing safety shutdown hook.");
205 shutdown(null, false);
206 } catch (PlatformException e) {
207 LOGGER.error("Simantics Platform shutdown hook execution failed.", e);
208 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Simantics Platform shutdown hook execution failed.", e));
214 * The {@link IProject} activated by
215 * {@link #startUp(IProgressMonitor, RecoveryPolicy, OntologyRecoveryPolicy, ServerAddress, PlatformUserAgent)}
217 private IProject project;
222 * Create a new simantics plaform manager in uninitialized state and
223 * with default policies. <p>
225 public SimanticsPlatform() {
226 log = Platform.getLog(Activator.getBundleContext().getBundle());
227 mainThread = Thread.currentThread();
230 public String getApplicationClientId() {
231 IProduct product = Platform.getProduct();
232 if(product == null) return "noProduct";//UUID.randomUUID().toString();
233 String application = product.getApplication();
234 return application != null ? application : UUID.randomUUID().toString();
237 private SessionDescriptor setupDatabase(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, PlatformUserAgent userAgent) throws PlatformException {
238 if (progressMonitor == null)
239 progressMonitor = new NullProgressMonitor();
240 Path workspaceLocation = Platform.getLocation().toFile().toPath();
241 Path dbLocation = dbLocation();
242 Path dbIniPath = workspaceLocation.resolve("db.ini");
243 // The driver file overrides any command line arguments to prevent
244 // using the wrong driver for an existing database directory.
245 ServerManager serverManager;
247 Ini dbIni = loadOrCreateDatabaseIni(dbIniPath, databaseDriverId);
248 databaseDriverId = dbIni.get("driver", "id");
249 serverManager = ServerManagerFactory.create(databaseDriverId, dbLocation.toAbsolutePath().toString());
250 } catch (DatabaseException | IOException e) {
251 throw new PlatformException("Failed to initialize database ServerManager with driver " + databaseDriverId, e);
253 progressMonitor.beginTask("Setting up Simantics Database", 100);
254 progressMonitor.setTaskName("Asserting Database is installed.");
255 String msg = "Failed to initialize Simantics database.";
258 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Initializing database at " + dbLocation + " with driver " + databaseDriverId));
259 progressMonitor.setTaskName("Creating database at " + dbLocation);
260 databasebManagement = serverManager.getManagement(dbLocation.toFile());
261 databasebManagement.create();
262 currentDatabaseDriver = databaseDriverId;
264 return serverManager.createDatabase(dbLocation.toFile());
265 } catch (DatabaseException e) {
266 throw new PlatformException(msg, e);
267 } catch (Throwable e) {
268 throw new PlatformException(msg, e);
270 progressMonitor.worked(20);
274 public void synchronizeOntologies(IProgressMonitor progressMonitor, OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize) throws PlatformException {
276 SubMonitor monitor = SubMonitor.convert(progressMonitor, 100);
278 monitor.setTaskName("Compile dynamic ontologies");
279 PlatformUtil.compileAllDynamicOntologies();
281 String message = "Asserting all ontologies are installed";
282 LOGGER.info(message);
283 monitor.setTaskName(message);
285 DatabaseManagement mgmt = new DatabaseManagement();
286 Map<GraphBundleRef, GraphBundleEx> platformTGs = new HashMap<>();
289 // Get a list of bundles installed into the database
290 message = "find installed bundles from database";
291 monitor.subTask(message);
292 LOGGER.info(message);
293 Map<GraphBundleRef, GraphBundleEx> installedTGs = new HashMap<>();
294 for (GraphBundle b : session.syncRequest( mgmt.GraphBundleQuery )) {
295 installedTGs.put(GraphBundleRef.of(b), GraphBundleEx.extend(b));
298 if(!requireSynchronize && installedTGs.size() > 1 && !Platform.inDevelopmentMode()) return;
299 // if(installedTGs.size() > 1) return;
301 // Get a list of all bundles in the platform (Bundle Context)
302 message = "load all transferable graphs from platform";
303 monitor.subTask(message);
304 LOGGER.info(message);
305 Collection<GraphBundle> tgs = PlatformUtil.getAllGraphs();
306 message = "extend bundles to compile versions";
307 monitor.subTask(message);
308 LOGGER.info(message);
309 for (GraphBundle b : tgs) {
310 GraphBundleEx gbe = GraphBundleEx.extend(b);
312 platformTGs.put(GraphBundleRef.of(b), gbe);
315 // Compile a list of TGs that need to be installed or reinstalled in the database
316 message = "check bundle reinstallation demand";
317 monitor.subTask(message);
318 LOGGER.info(message);
319 List<GraphBundleEx> installTGs = new ArrayList<>();
320 // Create list of TGs to update, <newTg, oldTg>
321 Map<GraphBundleEx,GraphBundleEx> reinstallTGs = new TreeMap<>();
322 for (Entry<GraphBundleRef, GraphBundleEx> e : platformTGs.entrySet()) {
323 GraphBundleRef key = e.getKey();
324 GraphBundleEx platformBundle = e.getValue();
325 GraphBundleEx existingBundle = installedTGs.get(key);
327 // System.out.println("GraphBundleRef key=" + key.toString());
329 if (existingBundle == null) {
330 // Bundle did not exist in the database, put it into list of bundles to install
331 installTGs.add(platformBundle);
334 // Bundle exists in the database
335 boolean platformBundleIsNewer = existingBundle.getVersion().compareTo(platformBundle.getVersion())<0;
336 if (!platformBundleIsNewer)
338 // Check hash of transferable graph to know whether to update or not.
339 if (platformBundle.getHashcode() == existingBundle.getHashcode())
341 //System.out.println("Ontology hashcodes do not match: platform bundle="
342 // + platformBundle.getVersionedId() + ", hash=" + platformBundle.getHashcode()
343 // + "; existing bundle=" + existingBundle.getVersionedId() + ", hash=" + existingBundle.getHashcode());
344 reinstallTGs.put(platformBundle, existingBundle);
348 // Database is missing graphs
349 if (!installTGs.isEmpty() || !reinstallTGs.isEmpty()) {
350 session.getService(XSupport.class).setServiceMode(true, true);
353 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError || ontologyPolicy == OntologyRecoveryPolicy.Bypass) {
354 StringBuilder sb = new StringBuilder("The following graphs are not installed in the database: ");
355 if (!installTGs.isEmpty()) {
357 for (GraphBundleEx e : installTGs) {
358 if (i>0) sb.append(", ");
360 sb.append(e.toString());
362 sb.append(" is missing from the database.\n");
364 if (!reinstallTGs.isEmpty()) {
366 for (Entry<GraphBundleEx, GraphBundleEx> e : reinstallTGs.entrySet()) {
367 if (i>0) sb.append(", ");
369 sb.append(e.getKey().toString());
371 sb.append(" Database/Platform Bundle version mismatch.\n");
373 sb.append("Hint: Use -fixErrors to install the graphs.");
374 if (ontologyPolicy == OntologyRecoveryPolicy.ThrowError)
375 throw new PlatformException(sb.toString());
377 log.log(new Status(IStatus.WARNING, Activator.PLUGIN_ID, sb.toString()));
380 // Reinstall database
381 if (ontologyPolicy == OntologyRecoveryPolicy.ReinstallDatabase) {
382 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Reinstalling the database."));
391 throw new PlatformException("Reinstalling Database, NOT IMPLEMENTED");
394 boolean serviceModeEntered = false;
396 if (ontologyPolicy == OntologyRecoveryPolicy.Merge) {
397 message = "Merging ontology changes";
398 monitor.subTask(message);
399 LOGGER.info(message);
400 // Sort missing TGs into install order
401 GraphDependencyAnalyzer<GraphBundle> analyzer = new GraphDependencyAnalyzer<GraphBundle>();
402 for(GraphBundle tg : installTGs) analyzer.addGraph(tg, tg.getGraph());
403 for(GraphBundle tg : reinstallTGs.keySet()) analyzer.addGraph(tg, tg.getGraph());
404 if(!analyzer.analyzeDependency()) {
405 Collection<Pair<GraphBundle, GraphBundle>> problems = analyzer.getConflicts();
406 StringBuilder sb = new StringBuilder();
407 for (Pair<GraphBundle, GraphBundle> problem : problems) {
408 sb.append("Conflict with "+problem.first+" and "+problem.second+".\n");
410 throw new PlatformException(sb.toString());
411 } else if(!session.syncRequest( analyzer.queryExternalDependenciesSatisfied )) {
412 Collection<IdentityNode> unsatisfiedDependencies = analyzer.getUnsatisfiedDependencies();
413 StringBuilder sb = new StringBuilder();
414 for (IdentityNode dep: unsatisfiedDependencies) {
415 sb.append("Unsatisfied Dependency "+dep+". Required by\n");
416 for(IU iu : GraphDependencyAnalyzer.toCollection(dep.getRequires())) {
417 sb.append(" " + ((GraphBundle)iu.getId()).getId() + "\n");
420 throw new PlatformException(sb.toString());
423 message = "Analyzed graph bundles";
424 monitor.subTask(message);
425 LOGGER.info(message);
427 List<GraphBundle> sortedBundles = analyzer.getSortedGraphs();
428 if(!sortedBundles.isEmpty()) {
430 session.syncRequest((Write) graph -> {
432 graph.newClusterSet(graph.getRootLibrary());
433 } catch (ClusterSetExistException e) {
434 // Cluster set exist already, no problem.
436 graph.setClusterSet4NewResource(graph.getRootLibrary());
437 graph.flushCluster();
440 boolean mergedOntologies = false;
443 for(final GraphBundle tg : sortedBundles) {
445 final IImportAdvisor advisor = new OntologyImportAdvisor(tg, mgmt);
446 final GraphBundle oldTG = reinstallTGs.get(tg);
448 boolean createImmutable = tg.getImmutable();
452 session.getService(XSupport.class).setServiceMode(true, createImmutable);
454 // Flush all queries once to allow even immutable request results to be invalidated
455 // because ontology installation affects immutable content and queries related to
456 // immutable content would not get invalidated at all otherwise.
457 if (!serviceModeEntered) {
458 serviceModeEntered = true;
459 session.getService(QueryControl.class).flush();
463 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Installing "+tg.toString()+" - "+tg.getName()));
464 ImportResult result = TransferableGraphs.importGraph1(session, new TGTransferableGraphSource(tg.getGraph()), advisor, null);
465 if (!result.missingExternals.isEmpty()) {
466 log.log(new Status(IStatus.ERROR, Activator.PLUGIN_ID, "Import of " + tg.toString() + " was missing the following external entities:\n" + EString.implode(result.missingExternals)));
473 startTransaction(session, false);
474 TransferableGraphDelta1 delta = new Diff(oldTG.getGraph(), tg.getGraph()).diff();
475 final long[] oldResources = oldTG.getResourceArray();
476 boolean changes = TransferableGraphs.hasChanges(readGraph(), oldResources, delta);
479 //log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Nothing to merge for "+tg.toString()));
483 log.log(new Status(IStatus.INFO, Activator.PLUGIN_ID, "Merging new version of "+tg.toString()));
485 startTransaction(session, true);
489 long[] resourceArray = TransferableGraphs.applyDelta(writeGraph(), oldResources, delta);
490 tg.setResourceArray(resourceArray);
491 mgmt.setGraphBundleEntry(tg);
493 mergedOntologies = true;
494 } catch (Throwable t) {
495 throw new PlatformException(t);
502 session.syncRequest((Write) graph -> {
503 graph.setClusterSet4NewResource(graph.getRootLibrary());
504 graph.flushCluster();
507 if (mergedOntologies)
508 DatabaseIndexing.deleteAllIndexes();
511 session.getService(XSupport.class).setServiceMode(false, false);
512 if (serviceModeEntered) {
513 // Flush all queries to ensure that queries that should now
514 // be immutable are not left as mutable in the query cache.
515 session.getService(QueryControl.class).flush();
518 message = "Ontologies synchronized";
519 monitor.subTask(message);
520 LOGGER.info(message);
522 } catch (IOException e) {
523 throw new PlatformException(e);
524 } catch (DatabaseException e) {
525 throw new PlatformException(e);
530 public boolean assertConfiguration(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy) throws PlatformException {
532 if (progressMonitor == null) progressMonitor = new NullProgressMonitor();
534 File workspaceLocation = Platform.getLocation().toFile();
536 boolean installProject = false;
537 progressMonitor.setTaskName("Asserting simantics.cfg is installed");
539 File propertyFile = new File(workspaceLocation, "simantics.cfg");
540 Properties properties;
542 properties = WorkspaceUtil.readProperties(propertyFile);
543 } catch (IOException e) {
544 if (workspacePolicy == RecoveryPolicy.ThrowError) throw new PlatformException("Could not load "+propertyFile);
546 // Create a project and write Property file
547 properties = new Properties();
548 properties.setProperty("project_uri", "http://Projects/Development%20Project");
549 properties.setProperty("project_name", "Development Project");
550 WorkspaceUtil.writeProperties(propertyFile, properties);
551 installProject |= true;
553 projectURI = properties.getProperty("project_uri");
554 projectName = properties.getProperty("project_name");
555 progressMonitor.worked(10);
556 } catch (IOException e) {
557 throw new PlatformException(e);
560 return installProject;
564 public boolean assertProject(IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy, boolean installProject) throws PlatformException {
566 SubMonitor monitor = SubMonitor.convert(progressMonitor, 10);
568 final DatabaseManagement mgmt = new DatabaseManagement();
570 monitor.setTaskName("Asserting project resource exists in the database");
572 projectResource = session.syncRequest(new PossibleResource(projectURI));
573 if (projectResource == null) {
574 // Project was not found
575 if (workspacePolicy == RecoveryPolicy.ThrowError)
576 throw new PlatformException("Project Resource "+projectURI+" is not found in the database.");
577 // Create empty project with no features
579 Transaction.startTransaction(session, true);
581 // The project needs to be created mutable.
582 session.getService(XSupport.class).setServiceMode(true, false);
584 ArrayList<String> empty = new ArrayList<String>();
585 projectResource = mgmt.createProject(projectName, empty);
586 installProject |= true;
588 session.getService(XSupport.class).setServiceMode(false, false);
589 Transaction.commit();
591 Transaction.endTransaction();
593 //session.getService( LifecycleSupport.class ).save();
594 } catch (DatabaseException e) {
595 throw new PlatformException("Failed to create "+projectURI, e);
598 } catch (DatabaseException e) {
599 throw new PlatformException("Failed to create "+projectURI, e);
603 return installProject;
607 public void updateInstalledGroups(IProgressMonitor progressMonitor, boolean installProject) throws PlatformException {
611 // Attach all feature groups available in platform to created project
612 progressMonitor.setTaskName("Install all features");
613 Set<GroupReference> publishedFeatureGroups = ProjectFeatures.getInstallGroupsOfPublishedFeatures();
614 Collection<GroupReference> groupsWithoutVersion = GroupReference.stripVersions(publishedFeatureGroups);
619 Projects.setProjectInstalledGroups(graph, projectResource, groupsWithoutVersion));
620 } catch (DatabaseException ae) {
621 throw new PlatformException("Failed to install features", ae);
623 progressMonitor.worked(10);
628 public void assertSessionModel(IProgressMonitor progressMonitor) throws PlatformException {
630 Properties properties = session.getService(Properties.class);
631 final String clientId = properties.getProperty("clientId");
635 // Currently this needs to be done before data becomes available
636 VirtualGraphSupport support = session.getService(VirtualGraphSupport.class);
637 VirtualGraph activations = support.getWorkspacePersistent("activations");
639 Resource sessionModel = session.syncRequest(new Read<Resource>() {
642 public Resource perform(ReadGraph graph) throws DatabaseException {
644 Layer0X L0X = Layer0X.getInstance(graph);
645 for(Resource sessionModel : graph.syncRequest(new ObjectsWithType(graph.getRootLibrary(), L0X.HasSession, L0X.Session))) {
646 String id = graph.getPossibleRelatedValue(sessionModel, L0X.Session_HasClientId);
647 if(id != null && id.equals(clientId)) return sessionModel;
655 if(sessionModel == null) {
657 sessionModel = session.syncRequest(new WriteResultRequest<Resource>(activations) {
660 public Resource perform(WriteGraph graph) throws DatabaseException {
661 Layer0 L0 = Layer0.getInstance(graph);
662 Layer0X L0X = Layer0X.getInstance(graph);
663 Resource session = graph.newResource();
664 graph.claim(session, L0.InstanceOf, null, L0X.Session);
665 graph.claim(session, L0X.Session_HasUser, null, graph.getResource("http://Users/AdminUser"));
666 graph.addLiteral(session, L0X.Session_HasClientId, L0X.Session_HasClientId_Inverse, clientId, Bindings.STRING);
667 graph.claim(graph.getRootLibrary(), L0X.HasSession, session);
674 session.registerService(SessionModel.class, new PlatformSessionModel(sessionModel));
675 } catch (DatabaseException e) {
676 throw new PlatformException(e);
681 static class PlatformSessionModel implements SessionModel {
682 private final Resource sessionModel;
684 public PlatformSessionModel(Resource model) {
685 this.sessionModel = model;
689 public Resource getResource() {
694 public void resetDatabase(IProgressMonitor monitor) throws PlatformException {
695 // TODO: fix this to use Path APIs
696 File dbLocation = dbLocation().toFile();
697 if(!dbLocation.exists())
700 Driver driver = Manager.getDriver("acorn");
701 Management management = driver.getManagement(dbLocation.getAbsolutePath(), null);
703 } catch (DatabaseException e) {
704 throw new PlatformException("Failed to remove database at " + dbLocation.getAbsolutePath(), e);
706 // We have created extra files to database folder which have to be deleted also.
707 // This is an awful idea! Do not create extra files to database folder!
709 for (int i=0; i<10; ++i) {
711 FileUtils.deleteAll(dbLocation);
714 } catch (IOException e) {
715 // Assuming this has been thrown because delete file/folder failed.
720 } catch (InterruptedException e) {
721 // Ignoring interrupted exception.
725 throw new PlatformException("Failed to remove database folder at " + dbLocation.getAbsolutePath(), t);
727 public void resetWorkspace(IProgressMonitor monitor, ArrayList<String> fileFilter) throws PlatformException, IllegalStateException, IOException {
728 File file = Platform.getLocation().toFile();
729 if (null != fileFilter)
730 FileUtils.deleteAllWithFilter(file , fileFilter);
731 resetDatabase(monitor);
734 private static Path tryGetInstallLocation() {
735 Location l = Platform.getInstallLocation();
736 return l == null ? null : new File(l.getURL().getPath()).toPath();
739 public Path databaseExists() {
740 Path dbLocation = dbLocation();
741 if(Files.exists(dbLocation))
746 public Path dbLocation() {
747 Path workspaceLocation = Platform.getLocation().toFile().toPath();
748 return workspaceLocation.resolve("db");
752 * Start-up the platform. The procedure consists of 8 steps. Once everything
753 * is up and running, all fields are set property.
756 * If workspacePolicy is FixErrors, there is an attempt to fix unexpected
757 * errors. It includes installing database files, installing ontologies, and
758 * installing project features.
761 * In Simantics Workbench this is handled in
762 * <code>SimanticsWorkbenchAdvisor#openWindows()</code>.
765 * If remote server is given, simantics plaform takes connection there
766 * instead of local server at "db/".
768 * @param workspacePolicy action to take on workspace/database related
770 * @param ontologyPolicy action to take on ontology mismatch
771 * @param progressMonitor optional progress monitor
772 * @param userAgent interface for resorting to user feedback during platform
773 * startup or <code>null</code> to resort to default measures
774 * @throws PlatformException
776 public synchronized SessionContext startUp(String databaseDriverId, IProgressMonitor progressMonitor, RecoveryPolicy workspacePolicy,
777 OntologyRecoveryPolicy ontologyPolicy, boolean requireSynchronize, PlatformUserAgent userAgent)
778 throws PlatformException
782 LOGGER.info("Beginning of SimanticsPlatform.startUp");
784 SubMonitor monitor = SubMonitor.convert(progressMonitor, 1000);
786 // For debugging on what kind of platform automatic tests are running in
787 // case there are problems.
788 if ("true".equals(System.getProperty("org.simantics.dumpBundleState")))
789 dumpPlatformBundleState();
791 // 0. Consult all startup extensions before doing anything with the workspace.
792 StartupExtensions.consultStartupExtensions();
793 LOGGER.info("Consulted platform pre-startup extensions");
795 // 0.1. Clear all temporary files
796 Simantics.clearTemporaryDirectory();
797 LOGGER.info("Cleared temporary directory");
799 // 0.2 Clear VariableRepository.repository static map which holds references to SessionImplDb
800 VariableRepository.clear();
802 // 0.2.1 Activate org.simantics.scl.osgi to prime the SCL compiler early.
803 @SuppressWarnings("unused")
804 ModuleRepository modRepo = SCLOsgi.MODULE_REPOSITORY;
806 // 0.3 Handle baseline database before opening db
807 @SuppressWarnings("unused")
808 boolean usingBaseline = DatabaseBaselines.handleBaselineDatabase(tryGetInstallLocation(), databaseExists() != null);
810 // 1. Assert there is a database at <workspace>/db
811 SessionDescriptor sessionDescriptor = setupDatabase(databaseDriverId, monitor.newChild(200, SubMonitor.SUPPRESS_NONE), workspacePolicy, userAgent);
812 session = sessionDescriptor.getSession();
813 LOGGER.info("Database setup complete");
815 // 2. Delete all indexes if we cannot be certain they are up-to-date
816 // A full index rebuild will be done later, before project activation.
817 XSupport support = session.getService(XSupport.class);
818 if (support.rolledback()) {
820 DatabaseIndexing.deleteAllIndexes();
821 } catch (IOException e) {
822 throw new PlatformException(e);
826 // 3. Assert all graphs, and correct versions, are installed to the database
827 synchronizeOntologies(monitor.newChild(400, SubMonitor.SUPPRESS_NONE), ontologyPolicy, requireSynchronize);
829 // 4. Assert simantics.cfg exists
830 boolean installProject = assertConfiguration(monitor.newChild(25, SubMonitor.SUPPRESS_NONE),workspacePolicy);
832 // 5. Assert Project Resource is installed in the database
833 installProject = assertProject(monitor.newChild(25, SubMonitor.SUPPRESS_NONE), workspacePolicy, installProject);
835 // 6. Install all features into project, if in debug mode
836 updateInstalledGroups(monitor.newChild(25), true); //installProject);
837 LOGGER.info("Installed all features into project");
839 // 7. Assert L0.Session in database for this session
840 assertSessionModel(monitor.newChild(25, SubMonitor.SUPPRESS_NONE));
842 session.getService(XSupport.class).setServiceMode(false, false);
845 String message = "Flush query cache";
846 monitor.setTaskName(message);
847 LOGGER.info(message);
848 session.syncRequest((Write) graph -> {
849 QueryControl qc = graph.getService(QueryControl.class);
852 } catch (DatabaseException e) {
853 LOGGER.error("Flushing queries failed.", e);
855 boolean loadProject = true;
857 String message = "Open database session";
858 monitor.setTaskName(message);
859 LOGGER.info(message);
860 sessionContext = SimanticsPlatform.INSTANCE.createSessionContext(true);
861 // This must be before setSessionContext since some listeners might query this
862 sessionContext.setHint(SimanticsKeys.KEY_PROJECT, SimanticsPlatform.INSTANCE.projectResource);
864 Simantics.setSessionContext(sessionContext);
866 // 1. Put ResourceBinding that throws an exception to General Bindings
867 message = "Put ResourceBinding that throws an exception to General Bindings";
868 LOGGER.info(message);
869 simanticsBindings = new SimanticsBindings();
870 Bindings.classBindingFactory.addFactory( simanticsBindings );
872 Session session = sessionContext.getSession();
873 session.registerService(Databoard.class, Bindings.databoard);
875 // Register datatype bindings
876 message = "Register datatype bindings";
877 LOGGER.info(message);
878 Bindings.defaultBindingFactory.getRepository().put(RGB.Integer.BINDING.type(), RGB.Integer.BINDING);
879 Bindings.defaultBindingFactory.getRepository().put(Font.BINDING.type(), Font.BINDING);
881 if (support.rolledback() || sessionDescriptor.isFreshDatabase()) {
882 message = "Rebuilding all indexes";
883 LOGGER.info(message);
884 monitor.setTaskName(message);
886 session.getService(IndexedRelations.class).fullRebuild(monitor.newChild(100), session);
887 } catch (IndexException e) {
888 LOGGER.error("Failed to re-build all indexes", e);
895 message = "Load project";
896 monitor.setTaskName(message);
897 LOGGER.info(message);
898 project = Projects.loadProject(sessionContext.getSession(), SimanticsPlatform.INSTANCE.projectResource);
899 sessionContext.setHint(ProjectKeys.KEY_PROJECT, project);
901 message = "Loading projects complete";
902 LOGGER.info(message);
904 message = "Activate project";
905 monitor.setTaskName(message);
906 LOGGER.info(message);
909 LOGGER.info("Project activated");
912 } catch (DatabaseException e) {
913 LOGGER.error("Platform startup failed.", e);
914 throw new PlatformException(e);
915 } catch (ProjectException e) {
916 boolean hasStackTrace = e.getStackTrace().length > 0;
918 throw new PlatformException(e.getMessage(), hasStackTrace);
919 throw new PlatformException(e, hasStackTrace);
924 // #7650: improve shutdown robustness in all applications that use the platform
925 Runtime.getRuntime().addShutdownHook(shutdownHook);
927 // Discard database session undo history at this point to prevent
928 // the user from undoing any initialization operations performed
929 // by the platform startup.
930 SimanticsPlatform.INSTANCE.discardSessionUndoHistory();
931 LOGGER.info("Discarded session undo history");
933 return sessionContext;
937 public void registerServices(SessionContext context) {
938 new GlobalServiceInitializer().initialize(session);
939 session.registerService(MergingGraphRequestProcessor.class, new MergingGraphRequestProcessor("SessionService", session, 20));
940 session.registerService(MergingDelayedWriteProcessor.class, new MergingDelayedWriteProcessor(session, 20));
944 public SessionContext createSessionContext(boolean init) throws PlatformException {
946 // Construct and initialize SessionContext from Session.
947 SessionContext sessionContext = SessionContext.create(session, init);
948 String message = "Session context created";
949 LOGGER.info(message);
951 registerServices(sessionContext);
952 message = "Session services registered";
953 LOGGER.info(message);
955 return sessionContext;
956 } catch (DatabaseException e) {
957 throw new PlatformException(e);
962 * Perform normal shutdown for the Simantics Platform.
964 * @param progressMonitor optional progress monitor
965 * @throws PlatformException
966 * @see {@link #shutdown(IProgressMonitor, boolean)}
968 public synchronized void shutdown(IProgressMonitor progressMonitor) throws PlatformException {
969 shutdown(progressMonitor, true);
973 * Shutdown Simantics Platform.
975 * In Simantics Workbench this is handled in
976 * <code>SimanticsWorkbenchAdvisor#disconnectFromWorkspace</code>.
978 * @param progressMonitor
979 * optional progress monitor
980 * @param clearTemporaryFiles
981 * allow or prevent deletion of temporary files at the end of the
983 * @throws PlatformException
985 public synchronized void shutdown(IProgressMonitor progressMonitor, boolean clearTemporaryFiles) throws PlatformException
987 SubMonitor progress = SubMonitor.convert(progressMonitor, 100);
988 PlatformException platformException = null;
990 progress.subTask("Close Project");
991 if (project != null) {
992 project.safeDispose();
997 TimedSessionCache.close();
999 progress.subTask("Thread pools");
1000 ThreadUtils.shutdown();
1001 ExecutorWorker.shutdown();
1005 progress.subTask("Close Database Session");
1006 if (sessionContext != null) {
1007 Session s = sessionContext.peekSession();
1009 progress.subTask("Flushing Index Caches");
1011 Simantics.flushIndexCaches(progress.newChild(20), s);
1012 } catch (Throwable t) {
1013 LOGGER.error("Failed to flush index caches.", t);
1017 progress.subTask("Close Database Session");
1018 sessionContext.safeDispose();
1019 sessionContext = null;
1020 Simantics.setSessionContext(null);
1022 if (simanticsBindings != null) {
1023 Bindings.classBindingFactory.removeFactory( simanticsBindings );
1024 simanticsBindings = null;
1027 // Make sure Simantics clipboard doesn't store unwanted session data references.
1028 Simantics.setClipboard(new SimanticsClipboardImpl());
1030 progress.worked(50);
1033 projectResource = null;
1034 currentDatabaseDriver = null;
1036 DependenciesRelation.assertFinishedTracking();
1038 } catch (Exception e) {
1039 platformException = new PlatformException("Failed to shutdown Simantics Platform", e);
1042 progress.worked(10);
1043 progress.subTask("Shutting down database");
1045 if (null != databasebManagement)
1046 databasebManagement.shutdown();
1047 } catch (Throwable t) {
1048 LOGGER.error("Database shutdown failed.", t);
1050 progress.worked(10);
1052 progress.subTask("Clear index status");
1054 // Everything ok, clear index dirty state.
1055 DatabaseIndexing.clearAllDirty();
1056 } catch (IOException e) {
1057 LOGGER.error("Problems encountered while refreshing database index states, see exception for details.", e);
1061 if (clearTemporaryFiles) {
1062 progress.subTask("Clearing Workspace Temporary Directory");
1064 Simantics.clearTemporaryDirectory();
1065 } catch (Throwable t) {
1066 LOGGER.error("Failed to clear the temporary directory.", t);
1069 progress.worked(10);
1070 if (null != platformException)
1071 throw platformException;
1073 // #7650: improve shutdown robustness in all applications that use the platform
1074 Runtime.getRuntime().removeShutdownHook(shutdownHook);
1077 // TODO: consider removing this in the future ??
1079 public void stateChanged(LifecycleState newState) {
1080 if(newState == LifecycleState.CLOSED) {
1082 if(Platform.isRunning()) {
1083 mainThread.interrupt();
1090 * @return <code>true</code> if discard was successful, <code>false</code>
1091 * if there was no session, {@link UndoRedoSupport} or
1092 * {@link UndoContext} to discard through
1094 public boolean discardSessionUndoHistory() {
1095 Session s = session;
1097 UndoRedoSupport urs = s.peekService(UndoRedoSupport.class);
1099 UndoContext uc = urs.getUndoContext(s);
1109 public void reconnect(String databaseDriverId) throws Exception {
1110 // Starts database server.
1111 if (currentDatabaseDriver != null)
1112 databaseDriverId = currentDatabaseDriver;
1113 SimanticsPlatform.INSTANCE.startUp(databaseDriverId, null, RecoveryPolicy.ThrowError, OntologyRecoveryPolicy.ThrowError, true, null);
1116 private void dumpPlatformBundleState() {
1117 BundleDescription[] bs = Platform.getPlatformAdmin().getState().getBundles();
1118 System.out.println("Total bundles: " + bs.length);
1119 for (BundleDescription b : bs) {
1120 System.out.format("%-80s @ %s\n", b.toString(), b.getLocation());
1124 private Ini loadOrCreateDatabaseIni(Path path, String databaseDriverId)
1125 throws InvalidFileFormatException, IOException
1127 File f = path.toFile();
1128 Ini dbIni = Files.isRegularFile(path) ? new Ini(f) : new Ini();
1129 String iniId = dbIni != null ? dbIni.get("driver", "id") : null;
1130 if (iniId == null) {
1131 dbIni.put("driver", "id", databaseDriverId);