1 /*******************************************************************************
2 * Copyright (c) 2007, 2010 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.workbench.internal;
15 import java.io.FileInputStream;
16 import java.io.FileOutputStream;
17 import java.io.IOException;
18 import java.io.OutputStream;
19 import java.net.MalformedURLException;
22 import java.util.Properties;
24 import org.eclipse.core.runtime.IConfigurationElement;
25 import org.eclipse.core.runtime.IExecutableExtension;
26 import org.eclipse.core.runtime.IStatus;
27 import org.eclipse.core.runtime.Platform;
28 import org.eclipse.core.runtime.Status;
29 import org.eclipse.equinox.app.IApplication;
30 import org.eclipse.equinox.app.IApplicationContext;
31 import org.eclipse.jface.dialogs.Dialog;
32 import org.eclipse.jface.dialogs.MessageDialog;
33 import org.eclipse.osgi.service.datalocation.Location;
34 import org.eclipse.osgi.util.NLS;
35 import org.eclipse.swt.SWT;
36 import org.eclipse.swt.widgets.Display;
37 import org.eclipse.swt.widgets.MessageBox;
38 import org.eclipse.swt.widgets.Shell;
39 import org.eclipse.ui.IWorkbench;
40 import org.eclipse.ui.PlatformUI;
41 import org.eclipse.ui.application.WorkbenchAdvisor;
42 import org.eclipse.ui.internal.WorkbenchPlugin;
43 import org.eclipse.ui.internal.ide.ChooseWorkspaceData;
44 import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog;
45 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
46 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
47 import org.eclipse.ui.internal.ide.StatusUtil;
48 import org.simantics.application.arguments.ApplicationUtils;
49 import org.simantics.application.arguments.Arguments;
50 import org.simantics.application.arguments.IArgumentFactory;
51 import org.simantics.application.arguments.IArguments;
52 import org.simantics.application.arguments.SimanticsArguments;
53 import org.simantics.db.management.ISessionContextProvider;
54 import org.simantics.db.management.ISessionContextProviderSource;
55 import org.simantics.db.management.SessionContextProvider;
56 import org.simantics.db.management.SingleSessionContextProviderSource;
57 import org.simantics.ui.SimanticsUI;
58 import org.simantics.utils.ui.BundleUtils;
59 import org.slf4j.Logger;
60 import org.slf4j.LoggerFactory;
64 * The "main program" for the Eclipse IDE.
68 public class SimanticsWorkbenchApplication implements IApplication, IExecutableExtension {
70 private static final Logger LOGGER = LoggerFactory.getLogger(SimanticsWorkbenchApplication.class);
72 * The name of the folder containing metadata information for the workspace.
74 public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
76 private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$
78 private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$
80 private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$
82 private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$
84 private static final String PROP_SHUTDOWN_GRACE_PERIOD = "simantics.shutdownGracePeriod"; //$NON-NLS-1$
85 private static final long DEFAULT_SHUTDOWN_GRACE_PERIOD = 5000L;
88 * A special return code that will be recognized by the launcher and used to
89 * restart the workbench.
91 private static final Integer EXIT_RELAUNCH = new Integer(24);
94 * A special return code that will be recognized by the PDE launcher and used to
95 * show an error dialog if the workspace is locked.
97 private static final Integer EXIT_WORKSPACE_LOCKED = new Integer(15);
100 * Creates a new IDE application.
102 public SimanticsWorkbenchApplication() {
103 // There is nothing to do for WorkbenchApplication
106 public WorkbenchAdvisor createWorkbenchAdvisor(IArguments args, DelayedEventsProcessor processor) {
107 return new SimanticsWorkbenchAdvisor(args, processor);
111 * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context)
114 public Object start(IApplicationContext appContext) throws Exception {
115 ApplicationUtils.loadSystemProperties(BundleUtils.find(Activator.PLUGIN_ID, "system.properties"));
116 IArguments args = parseArguments((String[]) appContext.getArguments().get(IApplicationContext.APPLICATION_ARGS));
118 Display display = createDisplay();
119 // processor must be created before we start event loop
120 DelayedEventsProcessor processor = new DelayedEventsProcessor(display);
123 Object argCheck = verifyArguments(args);
124 if (argCheck != null)
127 // look and see if there's a splash shell we can parent off of
128 Shell shell = WorkbenchPlugin.getSplashShell(display);
130 // should should set the icon and message for this shell to be the
131 // same as the chooser dialog - this will be the guy that lives in
132 // the task bar and without these calls you'd have the default icon
134 shell.setText(ChooseWorkspaceDialog.getWindowTitle());
135 shell.setImages(Dialog.getDefaultImages());
138 Object instanceLocationCheck = checkInstanceLocation(shell, appContext.getArguments(), args);
139 if (instanceLocationCheck != null) {
140 WorkbenchPlugin.unsetSplashShell(display);
141 Platform.endSplash();
142 return instanceLocationCheck;
145 final ISessionContextProvider provider = new SessionContextProvider(null);
146 final ISessionContextProviderSource contextProviderSource = new SingleSessionContextProviderSource(provider);
147 //final ISessionContextProviderSource contextProviderSource = new WorkbenchWindowSessionContextProviderSource(PlatformUI.getWorkbench());
148 SimanticsUI.setSessionContextProviderSource(contextProviderSource);
149 org.simantics.db.layer0.internal.SimanticsInternal.setSessionContextProviderSource(contextProviderSource);
150 org.simantics.Simantics.setSessionContextProviderSource(contextProviderSource);
152 // create the workbench with this advisor and run it until it exits
153 // N.B. createWorkbench remembers the advisor, and also registers
154 // the workbench globally so that all UI plug-ins can find it using
155 // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench()
156 int returnCode = PlatformUI.createAndRunWorkbench(display,
157 createWorkbenchAdvisor(args, processor));
159 Long shutdownGracePeriodPropValue = Long.getLong(PROP_SHUTDOWN_GRACE_PERIOD);
160 long shutdownGracePeriod = shutdownGracePeriodPropValue == null
161 ? DEFAULT_SHUTDOWN_GRACE_PERIOD
162 : shutdownGracePeriodPropValue;
164 // the workbench doesn't support relaunch yet (bug 61809) so
165 // for now restart is used, and exit data properties are checked
166 // here to substitute in the relaunch return code if needed
167 if (returnCode != PlatformUI.RETURN_RESTART) {
168 delayedShutdown(EXIT_OK, shutdownGracePeriod);
172 // if the exit code property has been set to the relaunch code, then
173 // return that code now, otherwise this is a normal restart
174 int exitCode = EXIT_RELAUNCH.equals(Integer.getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH
176 delayedShutdown(exitCode, shutdownGracePeriod);
179 if (display != null) {
182 Location instanceLoc = Platform.getInstanceLocation();
183 if (instanceLoc != null)
184 instanceLoc.release();
188 private void delayedShutdown(int exitCode, long delayMs) {
189 LOGGER.info("Started delayed shutdown with delay {} ms.", delayMs);
190 Thread shutdownThread = new Thread() {
194 Thread.sleep(delayMs);
195 LOGGER.warn("Delayed shutdown forced the application to exit with code {}.", exitCode);
196 // Method halt is used instead of System.exit, because running
197 // of shutdown hooks hangs the application in some cases.
198 Runtime.getRuntime().halt(exitCode);
199 } catch (InterruptedException e) {
204 shutdownThread.setDaemon(true);
205 shutdownThread.setName("delayed-shutdown");
206 shutdownThread.start();
209 /*************************************************************************/
211 private IArguments parseArguments(String[] args) {
212 IArgumentFactory<?>[] accepted = {
213 SimanticsArguments.RECOVERY_POLICY_FIX_ERRORS,
214 SimanticsArguments.ONTOLOGY_RECOVERY_POLICY_REINSTALL,
215 SimanticsArguments.DEFAULT_WORKSPACE_LOCATION,
216 SimanticsArguments.WORKSPACE_CHOOSER,
217 SimanticsArguments.WORKSPACE_NO_REMEMBER,
218 SimanticsArguments.PERSPECTIVE,
219 SimanticsArguments.SERVER,
220 SimanticsArguments.NEW_MODEL,
221 SimanticsArguments.EXPERIMENT,
222 SimanticsArguments.DISABLE_INDEX,
223 SimanticsArguments.DATABASE_ID,
224 SimanticsArguments.DO_NOT_SYNCHRONIZE_ONTOLOGIES
226 IArguments result = Arguments.parse(args, accepted);
230 private Object verifyArguments(IArguments args) {
231 StringBuilder report = new StringBuilder();
233 // if (args.contains(SimanticsArguments.NEW_PROJECT)) {
234 // if (args.contains(SimanticsArguments.PROJECT)) {
235 // exclusiveArguments(report, SimanticsArguments.PROJECT, SimanticsArguments.NEW_PROJECT);
237 // // Must have a server to checkout from when creating a new
238 // // project right from the beginning.
239 // if (!args.contains(SimanticsArguments.SERVER)) {
240 // missingArgument(report, SimanticsArguments.SERVER);
242 // } else if (args.contains(SimanticsArguments.PROJECT)) {
243 // // To load a project, a server must be defined to checkout from
244 // if (!args.contains(SimanticsArguments.SERVER)) {
245 // missingArgument(report, SimanticsArguments.SERVER);
249 // NEW_MODEL and MODEL arguments are optional
250 // EXPERIMENT argument is optional
252 String result = report.toString();
253 boolean valid = result.length() == 0;
256 String msg = NLS.bind(Messages.Application_1, result);
257 MessageDialog.openInformation(null, Messages.Application_2, msg);
259 return valid ? null : EXIT_OK;
262 // private void exclusiveArguments(StringBuilder sb, IArgumentFactory<?> arg1, IArgumentFactory<?> arg2) {
263 // sb.append(NLS.bind(Messages.Application_3, arg1.getArgument(), arg2.getArgument()));
267 // private void missingArgument(StringBuilder sb, IArgumentFactory<?> arg) {
268 // sb.append(NLS.bind(Messages.Application_0, arg.getArgument()));
272 /*************************************************************************/
275 * Creates the display used by the application.
277 * @return the display used by the application
279 protected Display createDisplay() {
280 return PlatformUI.createDisplay();
284 * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
287 public void setInitializationData(IConfigurationElement config,
288 String propertyName, Object data) {
289 // There is nothing to do for ProConfApplication
293 * Return true if a valid workspace path has been set and false otherwise.
294 * Prompt for and set the path if possible and required.
295 * @param applicationArguments
297 * @return true if a valid instance location has been set and false
300 private Object checkInstanceLocation(Shell shell, Map<?,?> applicationArguments, IArguments args) {
301 // -data @none was specified but an ide requires workspace
302 Location instanceLoc = Platform.getInstanceLocation();
303 if (instanceLoc == null) {
307 IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle,
308 IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage);
312 // -data "/valid/path", workspace already set
313 // This information is stored in configuration/.settings/org.eclipse.ui.ide.prefs
314 if (instanceLoc.isSet()) {
315 // make sure the meta data version is compatible (or the user has
316 // chosen to overwrite it).
317 if (!checkValidWorkspace(shell, instanceLoc.getURL())) {
321 // at this point its valid, so try to lock it and update the
322 // metadata version information if successful
324 if (instanceLoc.lock()) {
325 writeWorkspaceVersion();
329 // we failed to create the directory.
330 // Two possibilities:
331 // 1. directory is already in use
332 // 2. directory could not be created
333 File workspaceDirectory = new File(instanceLoc.getURL().getFile());
334 if (workspaceDirectory.exists()) {
335 if (isDevLaunchMode(applicationArguments)) {
336 return EXIT_WORKSPACE_LOCKED;
338 MessageDialog.openError(
340 IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle,
341 IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage);
343 MessageDialog.openError(
345 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
346 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
348 } catch (IOException e) {
349 IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$
354 IDEWorkbenchMessages.InternalError,
360 // -data @noDefault or -data not specified, prompt and set
361 ChooseWorkspaceData launchData = null;
362 if (args.contains(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)) {
363 launchData = new ChooseWorkspaceData(args.get(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION));
365 launchData = new ChooseWorkspaceData(instanceLoc.getDefault());
368 boolean force = args.contains(SimanticsArguments.WORKSPACE_CHOOSER);
369 boolean suppressAskAgain = args.contains(SimanticsArguments.WORKSPACE_NO_REMEMBER);
372 URL workspaceUrl = promptForWorkspace(shell, launchData, force, suppressAskAgain);
373 if (workspaceUrl == null) {
377 // if there is an error with the first selection, then force the
378 // dialog to open to give the user a chance to correct
382 // the operation will fail if the url is not a valid
383 // instance data area, so other checking is unneeded
384 if (instanceLoc.setURL(workspaceUrl, true)) {
385 launchData.writePersistedData();
386 writeWorkspaceVersion();
389 } catch (IllegalStateException e) {
393 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
394 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
398 // by this point it has been determined that the workspace is
399 // already in use -- force the user to choose again
400 MessageDialog.openError(shell, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle,
401 IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage);
405 private static boolean isDevLaunchMode(Map<?,?> args) {
406 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
407 if (Boolean.getBoolean("eclipse.pde.launch")) //$NON-NLS-1$
409 return args.containsKey("-pdelaunch"); //$NON-NLS-1$
412 private static class ChooseSimanticsWorkspaceDialog extends ChooseWorkspaceDialog {
414 public ChooseSimanticsWorkspaceDialog(Shell parentShell, ChooseWorkspaceData launchData, boolean suppressAskAgain, boolean centerOnMonitor) {
415 super(parentShell, launchData, suppressAskAgain, centerOnMonitor);
419 protected void configureShell(Shell shell) {
420 super.configureShell(shell);
421 // Use product name in shell title instead of generic "Eclipse Launcher"
422 shell.setText(getWindowTitle());
427 * Open a workspace selection dialog on the argument shell, populating the
428 * argument data with the user's selection. Perform first level validation
429 * on the selection by comparing the version information. This method does
430 * not examine the runtime state (e.g., is the workspace already locked?).
435 * setting to true makes the dialog open regardless of the
437 * @return An URL storing the selected workspace or null if the user has
438 * canceled the launch operation.
440 private URL promptForWorkspace(Shell shell, ChooseWorkspaceData launchData,
441 boolean force, boolean suppressAskAgain) {
444 // okay to use the shell now - this is the splash shell
445 new ChooseSimanticsWorkspaceDialog(shell, launchData, suppressAskAgain, true).prompt(force);
447 String instancePath = launchData.getSelection();
448 if (instancePath == null) {
452 // the dialog is not forced on the first iteration, but is on every
453 // subsequent one -- if there was an error then the user needs to be
457 // 70576: don't accept empty input
458 if (instancePath.length() <= 0) {
462 IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle,
463 IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage);
467 // create the workspace if it does not already exist
468 File workspace = new File(instancePath);
469 if (!workspace.exists()) {
474 // Don't use File.toURL() since it adds a leading slash that Platform does not
475 // handle properly. See bug 54081 for more details.
476 String path = workspace.getAbsolutePath().replace(
477 File.separatorChar, '/');
478 url = new URL("file", null, path); //$NON-NLS-1$
479 } catch (MalformedURLException e) {
483 IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle,
484 IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage);
487 } while (!checkValidWorkspace(shell, url));
493 * Return true if the argument directory is ok to use as a workspace and
494 * false otherwise. A version check will be performed, and a confirmation
495 * box may be displayed on the argument shell if an older version is
498 * @return true if the argument URL is ok to use as a workspace and false
501 private boolean checkValidWorkspace(Shell shell, URL url) {
502 // a null url is not a valid workspace
507 String version = readWorkspaceVersion(url);
509 // if the version could not be read, then there is not any existing
510 // workspace data to trample, e.g., perhaps its a new directory that
511 // is just starting to be used as a workspace
512 if (version == null) {
516 final int ide_version = Integer.parseInt(WORKSPACE_VERSION_VALUE);
517 int workspace_version = Integer.parseInt(version);
519 // equality test is required since any version difference (newer
520 // or older) may result in data being trampled
521 if (workspace_version == ide_version) {
525 // At this point workspace has been detected to be from a version
526 // other than the current ide version -- find out if the user wants
531 if (workspace_version < ide_version) {
532 // Workspace < IDE. Update must be possible without issues,
533 // so only inform user about it.
534 severity = MessageDialog.INFORMATION;
535 title = IDEWorkbenchMessages.IDEApplication_versionTitle_olderWorkspace;
536 message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_olderWorkspace, url.getFile());
538 // Workspace > IDE. It must have been opened with a newer IDE version.
539 // Downgrade might be problematic, so warn user about it.
540 severity = MessageDialog.WARNING;
541 title = IDEWorkbenchMessages.IDEApplication_versionTitle_newerWorkspace;
542 message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_newerWorkspace, url.getFile());
545 MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL
546 | SWT.ICON_WARNING | SWT.APPLICATION_MODAL);
548 mbox.setMessage(message);
549 return mbox.open() == SWT.OK;
553 * Look at the argument URL for the workspace's version information. Return
554 * that version if found and null otherwise.
556 private static String readWorkspaceVersion(URL workspace) {
557 File versionFile = getVersionFile(workspace, false);
558 if (versionFile == null || !versionFile.exists()) {
563 // Although the version file is not spec'ed to be a Java properties
564 // file, it happens to follow the same format currently, so using
565 // Properties to read it is convenient.
566 Properties props = new Properties();
567 FileInputStream is = new FileInputStream(versionFile);
574 return props.getProperty(WORKSPACE_VERSION_KEY);
575 } catch (IOException e) {
576 IDEWorkbenchPlugin.log("Could not read version file", new Status( //$NON-NLS-1$
577 IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH,
579 e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$,
586 * Write the version of the metadata into a known file overwriting any
587 * existing file contents. Writing the version file isn't really crucial,
588 * so the function is silent about failure
590 private static void writeWorkspaceVersion() {
591 Location instanceLoc = Platform.getInstanceLocation();
592 if (instanceLoc == null || instanceLoc.isReadOnly()) {
596 File versionFile = getVersionFile(instanceLoc.getURL(), true);
597 if (versionFile == null) {
601 OutputStream output = null;
603 String versionLine = WORKSPACE_VERSION_KEY + '='
604 + WORKSPACE_VERSION_VALUE;
606 output = new FileOutputStream(versionFile);
607 output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$
608 } catch (IOException e) {
609 IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$
610 StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e));
613 if (output != null) {
616 } catch (IOException e) {
623 * The version file is stored in the metadata area of the workspace. This
624 * method returns an URL to the file or null if the directory or file does
625 * not exist (and the create parameter is false).
628 * If the directory and file does not exist this parameter
629 * controls whether it will be created.
630 * @return An url to the file or null if the version file does not exist or
631 * could not be created.
633 private static File getVersionFile(URL workspaceUrl, boolean create) {
634 if (workspaceUrl == null) {
639 // make sure the directory exists
640 File metaDir = new File(workspaceUrl.getPath(), METADATA_FOLDER);
641 if (!metaDir.exists() && (!create || !metaDir.mkdir())) {
645 // make sure the file exists
646 File versionFile = new File(metaDir, VERSION_FILENAME);
647 if (!versionFile.exists()
648 && (!create || !versionFile.createNewFile())) {
653 } catch (IOException e) {
654 // cannot log because instance area has not been set
660 * @see org.eclipse.equinox.app.IApplication#stop()
664 final IWorkbench workbench = PlatformUI.getWorkbench();
665 if (workbench == null)
667 final Display display = workbench.getDisplay();
668 display.syncExec(new Runnable() {
671 if (!display.isDisposed())