1 /*******************************************************************************
\r
2 * Copyright (c) 2007, 2010 Association for Decentralized Information Management
\r
3 * in Industry THTH ry.
\r
4 * All rights reserved. This program and the accompanying materials
\r
5 * are made available under the terms of the Eclipse Public License v1.0
\r
6 * which accompanies this distribution, and is available at
\r
7 * http://www.eclipse.org/legal/epl-v10.html
\r
10 * VTT Technical Research Centre of Finland - initial API and implementation
\r
11 *******************************************************************************/
\r
12 package org.simantics.workbench.internal;
\r
14 import java.io.File;
\r
15 import java.io.FileInputStream;
\r
16 import java.io.FileOutputStream;
\r
17 import java.io.IOException;
\r
18 import java.io.OutputStream;
\r
19 import java.net.MalformedURLException;
\r
20 import java.net.URL;
\r
21 import java.util.Map;
\r
22 import java.util.Properties;
\r
24 import org.eclipse.core.runtime.IConfigurationElement;
\r
25 import org.eclipse.core.runtime.IExecutableExtension;
\r
26 import org.eclipse.core.runtime.IStatus;
\r
27 import org.eclipse.core.runtime.Platform;
\r
28 import org.eclipse.core.runtime.Status;
\r
29 import org.eclipse.equinox.app.IApplication;
\r
30 import org.eclipse.equinox.app.IApplicationContext;
\r
31 import org.eclipse.jface.dialogs.Dialog;
\r
32 import org.eclipse.jface.dialogs.MessageDialog;
\r
33 import org.eclipse.osgi.service.datalocation.Location;
\r
34 import org.eclipse.osgi.util.NLS;
\r
35 import org.eclipse.swt.SWT;
\r
36 import org.eclipse.swt.widgets.Display;
\r
37 import org.eclipse.swt.widgets.MessageBox;
\r
38 import org.eclipse.swt.widgets.Shell;
\r
39 import org.eclipse.ui.IWorkbench;
\r
40 import org.eclipse.ui.PlatformUI;
\r
41 import org.eclipse.ui.application.WorkbenchAdvisor;
\r
42 import org.eclipse.ui.internal.WorkbenchPlugin;
\r
43 import org.eclipse.ui.internal.ide.ChooseWorkspaceData;
\r
44 import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog;
\r
45 import org.eclipse.ui.internal.ide.IDEWorkbenchMessages;
\r
46 import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin;
\r
47 import org.eclipse.ui.internal.ide.StatusUtil;
\r
48 import org.simantics.application.arguments.ApplicationUtils;
\r
49 import org.simantics.application.arguments.Arguments;
\r
50 import org.simantics.application.arguments.IArgumentFactory;
\r
51 import org.simantics.application.arguments.IArguments;
\r
52 import org.simantics.application.arguments.SimanticsArguments;
\r
53 import org.simantics.db.management.ISessionContextProvider;
\r
54 import org.simantics.db.management.ISessionContextProviderSource;
\r
55 import org.simantics.db.management.SessionContextProvider;
\r
56 import org.simantics.db.management.SingleSessionContextProviderSource;
\r
57 import org.simantics.ui.SimanticsUI;
\r
58 import org.simantics.ui.WorkbenchWindowSessionContextProviderSource;
\r
59 import org.simantics.utils.ui.BundleUtils;
\r
63 * The "main program" for the Eclipse IDE.
\r
67 public class SimanticsWorkbenchApplication implements IApplication, IExecutableExtension {
\r
70 * The name of the folder containing metadata information for the workspace.
\r
72 public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$
\r
74 private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$
\r
76 private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$
\r
78 private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$
\r
80 private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$
\r
83 * A special return code that will be recognized by the launcher and used to
\r
84 * restart the workbench.
\r
86 private static final Integer EXIT_RELAUNCH = new Integer(24);
\r
89 * A special return code that will be recognized by the PDE launcher and used to
\r
90 * show an error dialog if the workspace is locked.
\r
92 private static final Integer EXIT_WORKSPACE_LOCKED = new Integer(15);
\r
95 * Creates a new IDE application.
\r
97 public SimanticsWorkbenchApplication() {
\r
98 // There is nothing to do for WorkbenchApplication
\r
101 public WorkbenchAdvisor createWorkbenchAdvisor(IArguments args, DelayedEventsProcessor processor) {
\r
102 return new SimanticsWorkbenchAdvisor(args, processor);
\r
106 * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context)
\r
109 public Object start(IApplicationContext appContext) throws Exception {
\r
110 ApplicationUtils.loadSystemProperties(BundleUtils.find(Activator.PLUGIN_ID, "system.properties"));
\r
111 IArguments args = parseArguments((String[]) appContext.getArguments().get(IApplicationContext.APPLICATION_ARGS));
\r
113 Display display = createDisplay();
\r
114 // processor must be created before we start event loop
\r
115 DelayedEventsProcessor processor = new DelayedEventsProcessor(display);
\r
118 Object argCheck = verifyArguments(args);
\r
119 if (argCheck != null)
\r
122 // look and see if there's a splash shell we can parent off of
\r
123 Shell shell = WorkbenchPlugin.getSplashShell(display);
\r
124 if (shell != null) {
\r
125 // should should set the icon and message for this shell to be the
\r
126 // same as the chooser dialog - this will be the guy that lives in
\r
127 // the task bar and without these calls you'd have the default icon
\r
128 // with no message.
\r
129 shell.setText(ChooseWorkspaceDialog.getWindowTitle());
\r
130 shell.setImages(Dialog.getDefaultImages());
\r
133 Object instanceLocationCheck = checkInstanceLocation(shell, appContext.getArguments(), args);
\r
134 if (instanceLocationCheck != null) {
\r
135 WorkbenchPlugin.unsetSplashShell(display);
\r
136 Platform.endSplash();
\r
137 return instanceLocationCheck;
\r
140 final ISessionContextProvider provider = new SessionContextProvider(null);
\r
141 final ISessionContextProviderSource contextProviderSource = new SingleSessionContextProviderSource(provider);
\r
142 //final ISessionContextProviderSource contextProviderSource = new WorkbenchWindowSessionContextProviderSource(PlatformUI.getWorkbench());
\r
143 SimanticsUI.setSessionContextProviderSource(contextProviderSource);
\r
144 org.simantics.db.layer0.internal.SimanticsInternal.setSessionContextProviderSource(contextProviderSource);
\r
145 org.simantics.Simantics.setSessionContextProviderSource(contextProviderSource);
\r
147 // create the workbench with this advisor and run it until it exits
\r
148 // N.B. createWorkbench remembers the advisor, and also registers
\r
149 // the workbench globally so that all UI plug-ins can find it using
\r
150 // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench()
\r
151 int returnCode = PlatformUI.createAndRunWorkbench(display,
\r
152 createWorkbenchAdvisor(args, processor));
\r
154 // the workbench doesn't support relaunch yet (bug 61809) so
\r
155 // for now restart is used, and exit data properties are checked
\r
156 // here to substitute in the relaunch return code if needed
\r
157 if (returnCode != PlatformUI.RETURN_RESTART) {
\r
161 // if the exit code property has been set to the relaunch code, then
\r
162 // return that code now, otherwise this is a normal restart
\r
163 return EXIT_RELAUNCH.equals(Integer.getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH
\r
166 if (display != null) {
\r
169 Location instanceLoc = Platform.getInstanceLocation();
\r
170 if (instanceLoc != null)
\r
171 instanceLoc.release();
\r
175 /*************************************************************************/
\r
177 private IArguments parseArguments(String[] args) {
\r
178 IArgumentFactory<?>[] accepted = {
\r
179 SimanticsArguments.RECOVERY_POLICY_FIX_ERRORS,
\r
180 SimanticsArguments.ONTOLOGY_RECOVERY_POLICY_REINSTALL,
\r
181 SimanticsArguments.DEFAULT_WORKSPACE_LOCATION,
\r
182 SimanticsArguments.WORKSPACE_CHOOSER,
\r
183 SimanticsArguments.WORKSPACE_NO_REMEMBER,
\r
184 SimanticsArguments.PERSPECTIVE,
\r
185 SimanticsArguments.SERVER,
\r
186 SimanticsArguments.NEW_MODEL,
\r
187 SimanticsArguments.EXPERIMENT,
\r
188 SimanticsArguments.DISABLE_INDEX,
\r
189 SimanticsArguments.DATABASE_ID,
\r
191 IArguments result = Arguments.parse(args, accepted);
\r
195 private Object verifyArguments(IArguments args) {
\r
196 StringBuilder report = new StringBuilder();
\r
198 // if (args.contains(SimanticsArguments.NEW_PROJECT)) {
\r
199 // if (args.contains(SimanticsArguments.PROJECT)) {
\r
200 // exclusiveArguments(report, SimanticsArguments.PROJECT, SimanticsArguments.NEW_PROJECT);
\r
202 // // Must have a server to checkout from when creating a new
\r
203 // // project right from the beginning.
\r
204 // if (!args.contains(SimanticsArguments.SERVER)) {
\r
205 // missingArgument(report, SimanticsArguments.SERVER);
\r
207 // } else if (args.contains(SimanticsArguments.PROJECT)) {
\r
208 // // To load a project, a server must be defined to checkout from
\r
209 // if (!args.contains(SimanticsArguments.SERVER)) {
\r
210 // missingArgument(report, SimanticsArguments.SERVER);
\r
214 // NEW_MODEL and MODEL arguments are optional
\r
215 // EXPERIMENT argument is optional
\r
217 String result = report.toString();
\r
218 boolean valid = result.length() == 0;
\r
221 String msg = NLS.bind(Messages.Application_1, result);
\r
222 MessageDialog.openInformation(null, Messages.Application_2, msg);
\r
224 return valid ? null : EXIT_OK;
\r
227 // private void exclusiveArguments(StringBuilder sb, IArgumentFactory<?> arg1, IArgumentFactory<?> arg2) {
\r
228 // sb.append(NLS.bind(Messages.Application_3, arg1.getArgument(), arg2.getArgument()));
\r
229 // sb.append('\n');
\r
232 // private void missingArgument(StringBuilder sb, IArgumentFactory<?> arg) {
\r
233 // sb.append(NLS.bind(Messages.Application_0, arg.getArgument()));
\r
234 // sb.append('\n');
\r
237 /*************************************************************************/
\r
240 * Creates the display used by the application.
\r
242 * @return the display used by the application
\r
244 protected Display createDisplay() {
\r
245 return PlatformUI.createDisplay();
\r
249 * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object)
\r
252 public void setInitializationData(IConfigurationElement config,
\r
253 String propertyName, Object data) {
\r
254 // There is nothing to do for ProConfApplication
\r
258 * Return true if a valid workspace path has been set and false otherwise.
\r
259 * Prompt for and set the path if possible and required.
\r
260 * @param applicationArguments
\r
262 * @return true if a valid instance location has been set and false
\r
265 private Object checkInstanceLocation(Shell shell, Map<?,?> applicationArguments, IArguments args) {
\r
266 // -data @none was specified but an ide requires workspace
\r
267 Location instanceLoc = Platform.getInstanceLocation();
\r
268 if (instanceLoc == null) {
\r
272 IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle,
\r
273 IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage);
\r
277 // -data "/valid/path", workspace already set
\r
278 // This information is stored in configuration/.settings/org.eclipse.ui.ide.prefs
\r
279 if (instanceLoc.isSet()) {
\r
280 // make sure the meta data version is compatible (or the user has
\r
281 // chosen to overwrite it).
\r
282 if (!checkValidWorkspace(shell, instanceLoc.getURL())) {
\r
286 // at this point its valid, so try to lock it and update the
\r
287 // metadata version information if successful
\r
289 if (instanceLoc.lock()) {
\r
290 writeWorkspaceVersion();
\r
294 // we failed to create the directory.
\r
295 // Two possibilities:
\r
296 // 1. directory is already in use
\r
297 // 2. directory could not be created
\r
298 File workspaceDirectory = new File(instanceLoc.getURL().getFile());
\r
299 if (workspaceDirectory.exists()) {
\r
300 if (isDevLaunchMode(applicationArguments)) {
\r
301 return EXIT_WORKSPACE_LOCKED;
\r
303 MessageDialog.openError(
\r
305 IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle,
\r
306 IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage);
\r
308 MessageDialog.openError(
\r
310 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
\r
311 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
\r
313 } catch (IOException e) {
\r
314 IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$
\r
319 IDEWorkbenchMessages.InternalError,
\r
325 // -data @noDefault or -data not specified, prompt and set
\r
326 ChooseWorkspaceData launchData = null;
\r
327 if (args.contains(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)) {
\r
328 launchData = new ChooseWorkspaceData(args.get(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION));
\r
330 launchData = new ChooseWorkspaceData(instanceLoc.getDefault());
\r
333 boolean force = args.contains(SimanticsArguments.WORKSPACE_CHOOSER);
\r
334 boolean suppressAskAgain = args.contains(SimanticsArguments.WORKSPACE_NO_REMEMBER);
\r
337 URL workspaceUrl = promptForWorkspace(shell, launchData, force, suppressAskAgain);
\r
338 if (workspaceUrl == null) {
\r
342 // if there is an error with the first selection, then force the
\r
343 // dialog to open to give the user a chance to correct
\r
347 // the operation will fail if the url is not a valid
\r
348 // instance data area, so other checking is unneeded
\r
349 if (instanceLoc.setURL(workspaceUrl, true)) {
\r
350 launchData.writePersistedData();
\r
351 writeWorkspaceVersion();
\r
354 } catch (IllegalStateException e) {
\r
358 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle,
\r
359 IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage);
\r
363 // by this point it has been determined that the workspace is
\r
364 // already in use -- force the user to choose again
\r
365 MessageDialog.openError(shell, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle,
\r
366 IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage);
\r
370 private static boolean isDevLaunchMode(Map<?,?> args) {
\r
371 // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode()
\r
372 if (Boolean.getBoolean("eclipse.pde.launch")) //$NON-NLS-1$
\r
374 return args.containsKey("-pdelaunch"); //$NON-NLS-1$
\r
378 * Open a workspace selection dialog on the argument shell, populating the
\r
379 * argument data with the user's selection. Perform first level validation
\r
380 * on the selection by comparing the version information. This method does
\r
381 * not examine the runtime state (e.g., is the workspace already locked?).
\r
384 * @param launchData
\r
386 * setting to true makes the dialog open regardless of the
\r
388 * @return An URL storing the selected workspace or null if the user has
\r
389 * canceled the launch operation.
\r
391 private URL promptForWorkspace(Shell shell, ChooseWorkspaceData launchData,
\r
392 boolean force, boolean suppressAskAgain) {
\r
395 // okay to use the shell now - this is the splash shell
\r
396 new ChooseWorkspaceDialog(shell, launchData, suppressAskAgain, true).prompt(force);
\r
397 String instancePath = launchData.getSelection();
\r
398 if (instancePath == null) {
\r
402 // the dialog is not forced on the first iteration, but is on every
\r
403 // subsequent one -- if there was an error then the user needs to be
\r
404 // allowed to fix it
\r
407 // 70576: don't accept empty input
\r
408 if (instancePath.length() <= 0) {
\r
412 IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle,
\r
413 IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage);
\r
417 // create the workspace if it does not already exist
\r
418 File workspace = new File(instancePath);
\r
419 if (!workspace.exists()) {
\r
424 // Don't use File.toURL() since it adds a leading slash that Platform does not
\r
425 // handle properly. See bug 54081 for more details.
\r
426 String path = workspace.getAbsolutePath().replace(
\r
427 File.separatorChar, '/');
\r
428 url = new URL("file", null, path); //$NON-NLS-1$
\r
429 } catch (MalformedURLException e) {
\r
433 IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle,
\r
434 IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage);
\r
437 } while (!checkValidWorkspace(shell, url));
\r
443 * Return true if the argument directory is ok to use as a workspace and
\r
444 * false otherwise. A version check will be performed, and a confirmation
\r
445 * box may be displayed on the argument shell if an older version is
\r
448 * @return true if the argument URL is ok to use as a workspace and false
\r
451 private boolean checkValidWorkspace(Shell shell, URL url) {
\r
452 // a null url is not a valid workspace
\r
457 String version = readWorkspaceVersion(url);
\r
459 // if the version could not be read, then there is not any existing
\r
460 // workspace data to trample, e.g., perhaps its a new directory that
\r
461 // is just starting to be used as a workspace
\r
462 if (version == null) {
\r
466 final int ide_version = Integer.parseInt(WORKSPACE_VERSION_VALUE);
\r
467 int workspace_version = Integer.parseInt(version);
\r
469 // equality test is required since any version difference (newer
\r
470 // or older) may result in data being trampled
\r
471 if (workspace_version == ide_version) {
\r
475 // At this point workspace has been detected to be from a version
\r
476 // other than the current ide version -- find out if the user wants
\r
477 // to use it anyhow.
\r
481 if (workspace_version < ide_version) {
\r
482 // Workspace < IDE. Update must be possible without issues,
\r
483 // so only inform user about it.
\r
484 severity = MessageDialog.INFORMATION;
\r
485 title = IDEWorkbenchMessages.IDEApplication_versionTitle_olderWorkspace;
\r
486 message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_olderWorkspace, url.getFile());
\r
488 // Workspace > IDE. It must have been opened with a newer IDE version.
\r
489 // Downgrade might be problematic, so warn user about it.
\r
490 severity = MessageDialog.WARNING;
\r
491 title = IDEWorkbenchMessages.IDEApplication_versionTitle_newerWorkspace;
\r
492 message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_newerWorkspace, url.getFile());
\r
495 MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL
\r
496 | SWT.ICON_WARNING | SWT.APPLICATION_MODAL);
\r
497 mbox.setText(title);
\r
498 mbox.setMessage(message);
\r
499 return mbox.open() == SWT.OK;
\r
503 * Look at the argument URL for the workspace's version information. Return
\r
504 * that version if found and null otherwise.
\r
506 private static String readWorkspaceVersion(URL workspace) {
\r
507 File versionFile = getVersionFile(workspace, false);
\r
508 if (versionFile == null || !versionFile.exists()) {
\r
513 // Although the version file is not spec'ed to be a Java properties
\r
514 // file, it happens to follow the same format currently, so using
\r
515 // Properties to read it is convenient.
\r
516 Properties props = new Properties();
\r
517 FileInputStream is = new FileInputStream(versionFile);
\r
524 return props.getProperty(WORKSPACE_VERSION_KEY);
\r
525 } catch (IOException e) {
\r
526 IDEWorkbenchPlugin.log("Could not read version file", new Status( //$NON-NLS-1$
\r
527 IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH,
\r
529 e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$,
\r
536 * Write the version of the metadata into a known file overwriting any
\r
537 * existing file contents. Writing the version file isn't really crucial,
\r
538 * so the function is silent about failure
\r
540 private static void writeWorkspaceVersion() {
\r
541 Location instanceLoc = Platform.getInstanceLocation();
\r
542 if (instanceLoc == null || instanceLoc.isReadOnly()) {
\r
546 File versionFile = getVersionFile(instanceLoc.getURL(), true);
\r
547 if (versionFile == null) {
\r
551 OutputStream output = null;
\r
553 String versionLine = WORKSPACE_VERSION_KEY + '='
\r
554 + WORKSPACE_VERSION_VALUE;
\r
556 output = new FileOutputStream(versionFile);
\r
557 output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$
\r
558 } catch (IOException e) {
\r
559 IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$
\r
560 StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e));
\r
563 if (output != null) {
\r
566 } catch (IOException e) {
\r
573 * The version file is stored in the metadata area of the workspace. This
\r
574 * method returns an URL to the file or null if the directory or file does
\r
575 * not exist (and the create parameter is false).
\r
578 * If the directory and file does not exist this parameter
\r
579 * controls whether it will be created.
\r
580 * @return An url to the file or null if the version file does not exist or
\r
581 * could not be created.
\r
583 private static File getVersionFile(URL workspaceUrl, boolean create) {
\r
584 if (workspaceUrl == null) {
\r
589 // make sure the directory exists
\r
590 File metaDir = new File(workspaceUrl.getPath(), METADATA_FOLDER);
\r
591 if (!metaDir.exists() && (!create || !metaDir.mkdir())) {
\r
595 // make sure the file exists
\r
596 File versionFile = new File(metaDir, VERSION_FILENAME);
\r
597 if (!versionFile.exists()
\r
598 && (!create || !versionFile.createNewFile())) {
\r
602 return versionFile;
\r
603 } catch (IOException e) {
\r
604 // cannot log because instance area has not been set
\r
610 * @see org.eclipse.equinox.app.IApplication#stop()
\r
613 public void stop() {
\r
614 final IWorkbench workbench = PlatformUI.getWorkbench();
\r
615 if (workbench == null)
\r
617 final Display display = workbench.getDisplay();
\r
618 display.syncExec(new Runnable() {
\r
620 public void run() {
\r
621 if (!display.isDisposed())
\r