X-Git-Url: https://gerrit.simantics.org/r/gitweb?p=simantics%2Fplatform.git;a=blobdiff_plain;f=bundles%2Forg.simantics.workbench%2Fsrc%2Forg%2Fsimantics%2Fworkbench%2Finternal%2FSimanticsWorkbenchApplication.java;h=8b0431f38e9cde7d6236b146ab5d239034eca37a;hp=cc0cc024e3314925b24b4b31e607d71dce28e4f9;hb=bcbe7aa23f42b82ff6fe4304943193c04858096c;hpb=e0723ff4f1103a4eafd74805be1d22b46f6a3494;ds=sidebyside diff --git a/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/SimanticsWorkbenchApplication.java b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/SimanticsWorkbenchApplication.java index cc0cc024e..8b0431f38 100644 --- a/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/SimanticsWorkbenchApplication.java +++ b/bundles/org.simantics.workbench/src/org/simantics/workbench/internal/SimanticsWorkbenchApplication.java @@ -1,627 +1,626 @@ -/******************************************************************************* - * Copyright (c) 2007, 2010 Association for Decentralized Information Management - * in Industry THTH ry. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * VTT Technical Research Centre of Finland - initial API and implementation - *******************************************************************************/ -package org.simantics.workbench.internal; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.util.Map; -import java.util.Properties; - -import org.eclipse.core.runtime.IConfigurationElement; -import org.eclipse.core.runtime.IExecutableExtension; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.Platform; -import org.eclipse.core.runtime.Status; -import org.eclipse.equinox.app.IApplication; -import org.eclipse.equinox.app.IApplicationContext; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.osgi.service.datalocation.Location; -import org.eclipse.osgi.util.NLS; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.MessageBox; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.ui.IWorkbench; -import org.eclipse.ui.PlatformUI; -import org.eclipse.ui.application.WorkbenchAdvisor; -import org.eclipse.ui.internal.WorkbenchPlugin; -import org.eclipse.ui.internal.ide.ChooseWorkspaceData; -import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog; -import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; -import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; -import org.eclipse.ui.internal.ide.StatusUtil; -import org.simantics.application.arguments.ApplicationUtils; -import org.simantics.application.arguments.Arguments; -import org.simantics.application.arguments.IArgumentFactory; -import org.simantics.application.arguments.IArguments; -import org.simantics.application.arguments.SimanticsArguments; -import org.simantics.db.management.ISessionContextProvider; -import org.simantics.db.management.ISessionContextProviderSource; -import org.simantics.db.management.SessionContextProvider; -import org.simantics.db.management.SingleSessionContextProviderSource; -import org.simantics.ui.SimanticsUI; -import org.simantics.ui.WorkbenchWindowSessionContextProviderSource; -import org.simantics.utils.ui.BundleUtils; - - -/** - * The "main program" for the Eclipse IDE. - * - * @since 3.0 - */ -public class SimanticsWorkbenchApplication implements IApplication, IExecutableExtension { - - /** - * The name of the folder containing metadata information for the workspace. - */ - public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$ - - private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$ - - private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$ - - private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$ - - private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$ - - /** - * A special return code that will be recognized by the launcher and used to - * restart the workbench. - */ - private static final Integer EXIT_RELAUNCH = new Integer(24); - - /** - * A special return code that will be recognized by the PDE launcher and used to - * show an error dialog if the workspace is locked. - */ - private static final Integer EXIT_WORKSPACE_LOCKED = new Integer(15); - - /** - * Creates a new IDE application. - */ - public SimanticsWorkbenchApplication() { - // There is nothing to do for WorkbenchApplication - } - - public WorkbenchAdvisor createWorkbenchAdvisor(IArguments args, DelayedEventsProcessor processor) { - return new SimanticsWorkbenchAdvisor(args, processor); - } - - /* (non-Javadoc) - * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context) - */ - @Override - public Object start(IApplicationContext appContext) throws Exception { - ApplicationUtils.loadSystemProperties(BundleUtils.find(Activator.PLUGIN_ID, "system.properties")); - IArguments args = parseArguments((String[]) appContext.getArguments().get(IApplicationContext.APPLICATION_ARGS)); - - Display display = createDisplay(); - // processor must be created before we start event loop - DelayedEventsProcessor processor = new DelayedEventsProcessor(display); - - try { - Object argCheck = verifyArguments(args); - if (argCheck != null) - return argCheck; - - // look and see if there's a splash shell we can parent off of - Shell shell = WorkbenchPlugin.getSplashShell(display); - if (shell != null) { - // should should set the icon and message for this shell to be the - // same as the chooser dialog - this will be the guy that lives in - // the task bar and without these calls you'd have the default icon - // with no message. - shell.setText(ChooseWorkspaceDialog.getWindowTitle()); - shell.setImages(Dialog.getDefaultImages()); - } - - Object instanceLocationCheck = checkInstanceLocation(shell, appContext.getArguments(), args); - if (instanceLocationCheck != null) { - WorkbenchPlugin.unsetSplashShell(display); - Platform.endSplash(); - return instanceLocationCheck; - } - - final ISessionContextProvider provider = new SessionContextProvider(null); - final ISessionContextProviderSource contextProviderSource = new SingleSessionContextProviderSource(provider); - //final ISessionContextProviderSource contextProviderSource = new WorkbenchWindowSessionContextProviderSource(PlatformUI.getWorkbench()); - SimanticsUI.setSessionContextProviderSource(contextProviderSource); - org.simantics.db.layer0.internal.SimanticsInternal.setSessionContextProviderSource(contextProviderSource); - org.simantics.Simantics.setSessionContextProviderSource(contextProviderSource); - - // create the workbench with this advisor and run it until it exits - // N.B. createWorkbench remembers the advisor, and also registers - // the workbench globally so that all UI plug-ins can find it using - // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench() - int returnCode = PlatformUI.createAndRunWorkbench(display, - createWorkbenchAdvisor(args, processor)); - - // the workbench doesn't support relaunch yet (bug 61809) so - // for now restart is used, and exit data properties are checked - // here to substitute in the relaunch return code if needed - if (returnCode != PlatformUI.RETURN_RESTART) { - return EXIT_OK; - } - - // if the exit code property has been set to the relaunch code, then - // return that code now, otherwise this is a normal restart - return EXIT_RELAUNCH.equals(Integer.getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH - : EXIT_RESTART; - } finally { - if (display != null) { - display.dispose(); - } - Location instanceLoc = Platform.getInstanceLocation(); - if (instanceLoc != null) - instanceLoc.release(); - } - } - - /*************************************************************************/ - - private IArguments parseArguments(String[] args) { - IArgumentFactory[] accepted = { - SimanticsArguments.RECOVERY_POLICY_FIX_ERRORS, - SimanticsArguments.ONTOLOGY_RECOVERY_POLICY_REINSTALL, - SimanticsArguments.DEFAULT_WORKSPACE_LOCATION, - SimanticsArguments.WORKSPACE_CHOOSER, - SimanticsArguments.WORKSPACE_NO_REMEMBER, - SimanticsArguments.PERSPECTIVE, - SimanticsArguments.SERVER, - SimanticsArguments.NEW_MODEL, - SimanticsArguments.EXPERIMENT, - SimanticsArguments.DISABLE_INDEX, - SimanticsArguments.DATABASE_ID, - }; - IArguments result = Arguments.parse(args, accepted); - return result; - } - - private Object verifyArguments(IArguments args) { - StringBuilder report = new StringBuilder(); - -// if (args.contains(SimanticsArguments.NEW_PROJECT)) { -// if (args.contains(SimanticsArguments.PROJECT)) { -// exclusiveArguments(report, SimanticsArguments.PROJECT, SimanticsArguments.NEW_PROJECT); -// } -// // Must have a server to checkout from when creating a new -// // project right from the beginning. -// if (!args.contains(SimanticsArguments.SERVER)) { -// missingArgument(report, SimanticsArguments.SERVER); -// } -// } else if (args.contains(SimanticsArguments.PROJECT)) { -// // To load a project, a server must be defined to checkout from -// if (!args.contains(SimanticsArguments.SERVER)) { -// missingArgument(report, SimanticsArguments.SERVER); -// } -// } - - // NEW_MODEL and MODEL arguments are optional - // EXPERIMENT argument is optional - - String result = report.toString(); - boolean valid = result.length() == 0; - - if (!valid) { - String msg = NLS.bind(Messages.Application_1, result); - MessageDialog.openInformation(null, Messages.Application_2, msg); - } - return valid ? null : EXIT_OK; - } - -// private void exclusiveArguments(StringBuilder sb, IArgumentFactory arg1, IArgumentFactory arg2) { -// sb.append(NLS.bind(Messages.Application_3, arg1.getArgument(), arg2.getArgument())); -// sb.append('\n'); -// } -// -// private void missingArgument(StringBuilder sb, IArgumentFactory arg) { -// sb.append(NLS.bind(Messages.Application_0, arg.getArgument())); -// sb.append('\n'); -// } - - /*************************************************************************/ - - /** - * Creates the display used by the application. - * - * @return the display used by the application - */ - protected Display createDisplay() { - return PlatformUI.createDisplay(); - } - - /* (non-Javadoc) - * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object) - */ - @Override - public void setInitializationData(IConfigurationElement config, - String propertyName, Object data) { - // There is nothing to do for ProConfApplication - } - - /** - * Return true if a valid workspace path has been set and false otherwise. - * Prompt for and set the path if possible and required. - * @param applicationArguments - * - * @return true if a valid instance location has been set and false - * otherwise - */ - private Object checkInstanceLocation(Shell shell, Map applicationArguments, IArguments args) { - // -data @none was specified but an ide requires workspace - Location instanceLoc = Platform.getInstanceLocation(); - if (instanceLoc == null) { - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle, - IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage); - return EXIT_OK; - } - - // -data "/valid/path", workspace already set - // This information is stored in configuration/.settings/org.eclipse.ui.ide.prefs - if (instanceLoc.isSet()) { - // make sure the meta data version is compatible (or the user has - // chosen to overwrite it). - if (!checkValidWorkspace(shell, instanceLoc.getURL())) { - return EXIT_OK; - } - - // at this point its valid, so try to lock it and update the - // metadata version information if successful - try { - if (instanceLoc.lock()) { - writeWorkspaceVersion(); - return null; - } - - // we failed to create the directory. - // Two possibilities: - // 1. directory is already in use - // 2. directory could not be created - File workspaceDirectory = new File(instanceLoc.getURL().getFile()); - if (workspaceDirectory.exists()) { - if (isDevLaunchMode(applicationArguments)) { - return EXIT_WORKSPACE_LOCKED; - } - MessageDialog.openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle, - IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage); - } else { - MessageDialog.openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); - } - } catch (IOException e) { - IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$ - e); - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.InternalError, - e.getMessage()); - } - return EXIT_OK; - } - - // -data @noDefault or -data not specified, prompt and set - ChooseWorkspaceData launchData = null; - if (args.contains(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)) { - launchData = new ChooseWorkspaceData(args.get(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)); - } else { - launchData = new ChooseWorkspaceData(instanceLoc.getDefault()); - } - - boolean force = args.contains(SimanticsArguments.WORKSPACE_CHOOSER); - boolean suppressAskAgain = args.contains(SimanticsArguments.WORKSPACE_NO_REMEMBER); - - while (true) { - URL workspaceUrl = promptForWorkspace(shell, launchData, force, suppressAskAgain); - if (workspaceUrl == null) { - return EXIT_OK; - } - - // if there is an error with the first selection, then force the - // dialog to open to give the user a chance to correct - force = true; - - try { - // the operation will fail if the url is not a valid - // instance data area, so other checking is unneeded - if (instanceLoc.setURL(workspaceUrl, true)) { - launchData.writePersistedData(); - writeWorkspaceVersion(); - return null; - } - } catch (IllegalStateException e) { - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, - IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); - return EXIT_OK; - } - - // by this point it has been determined that the workspace is - // already in use -- force the user to choose again - MessageDialog.openError(shell, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle, - IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage); - } - } - - private static boolean isDevLaunchMode(Map args) { - // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode() - if (Boolean.getBoolean("eclipse.pde.launch")) //$NON-NLS-1$ - return true; - return args.containsKey("-pdelaunch"); //$NON-NLS-1$ - } - - /** - * Open a workspace selection dialog on the argument shell, populating the - * argument data with the user's selection. Perform first level validation - * on the selection by comparing the version information. This method does - * not examine the runtime state (e.g., is the workspace already locked?). - * - * @param shell - * @param launchData - * @param force - * setting to true makes the dialog open regardless of the - * showDialog value - * @return An URL storing the selected workspace or null if the user has - * canceled the launch operation. - */ - private URL promptForWorkspace(Shell shell, ChooseWorkspaceData launchData, - boolean force, boolean suppressAskAgain) { - URL url = null; - do { - // okay to use the shell now - this is the splash shell - new ChooseWorkspaceDialog(shell, launchData, suppressAskAgain, true).prompt(force); - String instancePath = launchData.getSelection(); - if (instancePath == null) { - return null; - } - - // the dialog is not forced on the first iteration, but is on every - // subsequent one -- if there was an error then the user needs to be - // allowed to fix it - force = true; - - // 70576: don't accept empty input - if (instancePath.length() <= 0) { - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle, - IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage); - continue; - } - - // create the workspace if it does not already exist - File workspace = new File(instancePath); - if (!workspace.exists()) { - workspace.mkdir(); - } - - try { - // Don't use File.toURL() since it adds a leading slash that Platform does not - // handle properly. See bug 54081 for more details. - String path = workspace.getAbsolutePath().replace( - File.separatorChar, '/'); - url = new URL("file", null, path); //$NON-NLS-1$ - } catch (MalformedURLException e) { - MessageDialog - .openError( - shell, - IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle, - IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage); - continue; - } - } while (!checkValidWorkspace(shell, url)); - - return url; - } - - /** - * Return true if the argument directory is ok to use as a workspace and - * false otherwise. A version check will be performed, and a confirmation - * box may be displayed on the argument shell if an older version is - * detected. - * - * @return true if the argument URL is ok to use as a workspace and false - * otherwise. - */ - private boolean checkValidWorkspace(Shell shell, URL url) { - // a null url is not a valid workspace - if (url == null) { - return false; - } - - String version = readWorkspaceVersion(url); - - // if the version could not be read, then there is not any existing - // workspace data to trample, e.g., perhaps its a new directory that - // is just starting to be used as a workspace - if (version == null) { - return true; - } - - final int ide_version = Integer.parseInt(WORKSPACE_VERSION_VALUE); - int workspace_version = Integer.parseInt(version); - - // equality test is required since any version difference (newer - // or older) may result in data being trampled - if (workspace_version == ide_version) { - return true; - } - - // At this point workspace has been detected to be from a version - // other than the current ide version -- find out if the user wants - // to use it anyhow. - int severity; - String title; - String message; - if (workspace_version < ide_version) { - // Workspace < IDE. Update must be possible without issues, - // so only inform user about it. - severity = MessageDialog.INFORMATION; - title = IDEWorkbenchMessages.IDEApplication_versionTitle_olderWorkspace; - message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_olderWorkspace, url.getFile()); - } else { - // Workspace > IDE. It must have been opened with a newer IDE version. - // Downgrade might be problematic, so warn user about it. - severity = MessageDialog.WARNING; - title = IDEWorkbenchMessages.IDEApplication_versionTitle_newerWorkspace; - message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_newerWorkspace, url.getFile()); - } - - MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL - | SWT.ICON_WARNING | SWT.APPLICATION_MODAL); - mbox.setText(title); - mbox.setMessage(message); - return mbox.open() == SWT.OK; - } - - /** - * Look at the argument URL for the workspace's version information. Return - * that version if found and null otherwise. - */ - private static String readWorkspaceVersion(URL workspace) { - File versionFile = getVersionFile(workspace, false); - if (versionFile == null || !versionFile.exists()) { - return null; - } - - try { - // Although the version file is not spec'ed to be a Java properties - // file, it happens to follow the same format currently, so using - // Properties to read it is convenient. - Properties props = new Properties(); - FileInputStream is = new FileInputStream(versionFile); - try { - props.load(is); - } finally { - is.close(); - } - - return props.getProperty(WORKSPACE_VERSION_KEY); - } catch (IOException e) { - IDEWorkbenchPlugin.log("Could not read version file", new Status( //$NON-NLS-1$ - IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, - IStatus.ERROR, - e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$, - e)); - return null; - } - } - - /** - * Write the version of the metadata into a known file overwriting any - * existing file contents. Writing the version file isn't really crucial, - * so the function is silent about failure - */ - private static void writeWorkspaceVersion() { - Location instanceLoc = Platform.getInstanceLocation(); - if (instanceLoc == null || instanceLoc.isReadOnly()) { - return; - } - - File versionFile = getVersionFile(instanceLoc.getURL(), true); - if (versionFile == null) { - return; - } - - OutputStream output = null; - try { - String versionLine = WORKSPACE_VERSION_KEY + '=' - + WORKSPACE_VERSION_VALUE; - - output = new FileOutputStream(versionFile); - output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$ - } catch (IOException e) { - IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$ - StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); - } finally { - try { - if (output != null) { - output.close(); - } - } catch (IOException e) { - // do nothing - } - } - } - - /** - * The version file is stored in the metadata area of the workspace. This - * method returns an URL to the file or null if the directory or file does - * not exist (and the create parameter is false). - * - * @param create - * If the directory and file does not exist this parameter - * controls whether it will be created. - * @return An url to the file or null if the version file does not exist or - * could not be created. - */ - private static File getVersionFile(URL workspaceUrl, boolean create) { - if (workspaceUrl == null) { - return null; - } - - try { - // make sure the directory exists - File metaDir = new File(workspaceUrl.getPath(), METADATA_FOLDER); - if (!metaDir.exists() && (!create || !metaDir.mkdir())) { - return null; - } - - // make sure the file exists - File versionFile = new File(metaDir, VERSION_FILENAME); - if (!versionFile.exists() - && (!create || !versionFile.createNewFile())) { - return null; - } - - return versionFile; - } catch (IOException e) { - // cannot log because instance area has not been set - return null; - } - } - - /* (non-Javadoc) - * @see org.eclipse.equinox.app.IApplication#stop() - */ - @Override - public void stop() { - final IWorkbench workbench = PlatformUI.getWorkbench(); - if (workbench == null) - return; - final Display display = workbench.getDisplay(); - display.syncExec(new Runnable() { - @Override - public void run() { - if (!display.isDisposed()) - workbench.close(); - } - }); - } - -} +/******************************************************************************* + * Copyright (c) 2007, 2010 Association for Decentralized Information Management + * in Industry THTH ry. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * VTT Technical Research Centre of Finland - initial API and implementation + *******************************************************************************/ +package org.simantics.workbench.internal; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.Map; +import java.util.Properties; + +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExecutableExtension; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.Status; +import org.eclipse.equinox.app.IApplication; +import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.osgi.service.datalocation.Location; +import org.eclipse.osgi.util.NLS; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.MessageBox; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.ui.IWorkbench; +import org.eclipse.ui.PlatformUI; +import org.eclipse.ui.application.WorkbenchAdvisor; +import org.eclipse.ui.internal.WorkbenchPlugin; +import org.eclipse.ui.internal.ide.ChooseWorkspaceData; +import org.eclipse.ui.internal.ide.ChooseWorkspaceDialog; +import org.eclipse.ui.internal.ide.IDEWorkbenchMessages; +import org.eclipse.ui.internal.ide.IDEWorkbenchPlugin; +import org.eclipse.ui.internal.ide.StatusUtil; +import org.simantics.application.arguments.ApplicationUtils; +import org.simantics.application.arguments.Arguments; +import org.simantics.application.arguments.IArgumentFactory; +import org.simantics.application.arguments.IArguments; +import org.simantics.application.arguments.SimanticsArguments; +import org.simantics.db.management.ISessionContextProvider; +import org.simantics.db.management.ISessionContextProviderSource; +import org.simantics.db.management.SessionContextProvider; +import org.simantics.db.management.SingleSessionContextProviderSource; +import org.simantics.ui.SimanticsUI; +import org.simantics.utils.ui.BundleUtils; + + +/** + * The "main program" for the Eclipse IDE. + * + * @since 3.0 + */ +public class SimanticsWorkbenchApplication implements IApplication, IExecutableExtension { + + /** + * The name of the folder containing metadata information for the workspace. + */ + public static final String METADATA_FOLDER = ".metadata"; //$NON-NLS-1$ + + private static final String VERSION_FILENAME = "version.ini"; //$NON-NLS-1$ + + private static final String WORKSPACE_VERSION_KEY = "org.eclipse.core.runtime"; //$NON-NLS-1$ + + private static final String WORKSPACE_VERSION_VALUE = "1"; //$NON-NLS-1$ + + private static final String PROP_EXIT_CODE = "eclipse.exitcode"; //$NON-NLS-1$ + + /** + * A special return code that will be recognized by the launcher and used to + * restart the workbench. + */ + private static final Integer EXIT_RELAUNCH = new Integer(24); + + /** + * A special return code that will be recognized by the PDE launcher and used to + * show an error dialog if the workspace is locked. + */ + private static final Integer EXIT_WORKSPACE_LOCKED = new Integer(15); + + /** + * Creates a new IDE application. + */ + public SimanticsWorkbenchApplication() { + // There is nothing to do for WorkbenchApplication + } + + public WorkbenchAdvisor createWorkbenchAdvisor(IArguments args, DelayedEventsProcessor processor) { + return new SimanticsWorkbenchAdvisor(args, processor); + } + + /* (non-Javadoc) + * @see org.eclipse.equinox.app.IApplication#start(org.eclipse.equinox.app.IApplicationContext context) + */ + @Override + public Object start(IApplicationContext appContext) throws Exception { + ApplicationUtils.loadSystemProperties(BundleUtils.find(Activator.PLUGIN_ID, "system.properties")); + IArguments args = parseArguments((String[]) appContext.getArguments().get(IApplicationContext.APPLICATION_ARGS)); + + Display display = createDisplay(); + // processor must be created before we start event loop + DelayedEventsProcessor processor = new DelayedEventsProcessor(display); + + try { + Object argCheck = verifyArguments(args); + if (argCheck != null) + return argCheck; + + // look and see if there's a splash shell we can parent off of + Shell shell = WorkbenchPlugin.getSplashShell(display); + if (shell != null) { + // should should set the icon and message for this shell to be the + // same as the chooser dialog - this will be the guy that lives in + // the task bar and without these calls you'd have the default icon + // with no message. + shell.setText(ChooseWorkspaceDialog.getWindowTitle()); + shell.setImages(Dialog.getDefaultImages()); + } + + Object instanceLocationCheck = checkInstanceLocation(shell, appContext.getArguments(), args); + if (instanceLocationCheck != null) { + WorkbenchPlugin.unsetSplashShell(display); + Platform.endSplash(); + return instanceLocationCheck; + } + + final ISessionContextProvider provider = new SessionContextProvider(null); + final ISessionContextProviderSource contextProviderSource = new SingleSessionContextProviderSource(provider); + //final ISessionContextProviderSource contextProviderSource = new WorkbenchWindowSessionContextProviderSource(PlatformUI.getWorkbench()); + SimanticsUI.setSessionContextProviderSource(contextProviderSource); + org.simantics.db.layer0.internal.SimanticsInternal.setSessionContextProviderSource(contextProviderSource); + org.simantics.Simantics.setSessionContextProviderSource(contextProviderSource); + + // create the workbench with this advisor and run it until it exits + // N.B. createWorkbench remembers the advisor, and also registers + // the workbench globally so that all UI plug-ins can find it using + // PlatformUI.getWorkbench() or AbstractUIPlugin.getWorkbench() + int returnCode = PlatformUI.createAndRunWorkbench(display, + createWorkbenchAdvisor(args, processor)); + + // the workbench doesn't support relaunch yet (bug 61809) so + // for now restart is used, and exit data properties are checked + // here to substitute in the relaunch return code if needed + if (returnCode != PlatformUI.RETURN_RESTART) { + return EXIT_OK; + } + + // if the exit code property has been set to the relaunch code, then + // return that code now, otherwise this is a normal restart + return EXIT_RELAUNCH.equals(Integer.getInteger(PROP_EXIT_CODE)) ? EXIT_RELAUNCH + : EXIT_RESTART; + } finally { + if (display != null) { + display.dispose(); + } + Location instanceLoc = Platform.getInstanceLocation(); + if (instanceLoc != null) + instanceLoc.release(); + } + } + + /*************************************************************************/ + + private IArguments parseArguments(String[] args) { + IArgumentFactory[] accepted = { + SimanticsArguments.RECOVERY_POLICY_FIX_ERRORS, + SimanticsArguments.ONTOLOGY_RECOVERY_POLICY_REINSTALL, + SimanticsArguments.DEFAULT_WORKSPACE_LOCATION, + SimanticsArguments.WORKSPACE_CHOOSER, + SimanticsArguments.WORKSPACE_NO_REMEMBER, + SimanticsArguments.PERSPECTIVE, + SimanticsArguments.SERVER, + SimanticsArguments.NEW_MODEL, + SimanticsArguments.EXPERIMENT, + SimanticsArguments.DISABLE_INDEX, + SimanticsArguments.DATABASE_ID, + }; + IArguments result = Arguments.parse(args, accepted); + return result; + } + + private Object verifyArguments(IArguments args) { + StringBuilder report = new StringBuilder(); + +// if (args.contains(SimanticsArguments.NEW_PROJECT)) { +// if (args.contains(SimanticsArguments.PROJECT)) { +// exclusiveArguments(report, SimanticsArguments.PROJECT, SimanticsArguments.NEW_PROJECT); +// } +// // Must have a server to checkout from when creating a new +// // project right from the beginning. +// if (!args.contains(SimanticsArguments.SERVER)) { +// missingArgument(report, SimanticsArguments.SERVER); +// } +// } else if (args.contains(SimanticsArguments.PROJECT)) { +// // To load a project, a server must be defined to checkout from +// if (!args.contains(SimanticsArguments.SERVER)) { +// missingArgument(report, SimanticsArguments.SERVER); +// } +// } + + // NEW_MODEL and MODEL arguments are optional + // EXPERIMENT argument is optional + + String result = report.toString(); + boolean valid = result.length() == 0; + + if (!valid) { + String msg = NLS.bind(Messages.Application_1, result); + MessageDialog.openInformation(null, Messages.Application_2, msg); + } + return valid ? null : EXIT_OK; + } + +// private void exclusiveArguments(StringBuilder sb, IArgumentFactory arg1, IArgumentFactory arg2) { +// sb.append(NLS.bind(Messages.Application_3, arg1.getArgument(), arg2.getArgument())); +// sb.append('\n'); +// } +// +// private void missingArgument(StringBuilder sb, IArgumentFactory arg) { +// sb.append(NLS.bind(Messages.Application_0, arg.getArgument())); +// sb.append('\n'); +// } + + /*************************************************************************/ + + /** + * Creates the display used by the application. + * + * @return the display used by the application + */ + protected Display createDisplay() { + return PlatformUI.createDisplay(); + } + + /* (non-Javadoc) + * @see org.eclipse.core.runtime.IExecutableExtension#setInitializationData(org.eclipse.core.runtime.IConfigurationElement, java.lang.String, java.lang.Object) + */ + @Override + public void setInitializationData(IConfigurationElement config, + String propertyName, Object data) { + // There is nothing to do for ProConfApplication + } + + /** + * Return true if a valid workspace path has been set and false otherwise. + * Prompt for and set the path if possible and required. + * @param applicationArguments + * + * @return true if a valid instance location has been set and false + * otherwise + */ + private Object checkInstanceLocation(Shell shell, Map applicationArguments, IArguments args) { + // -data @none was specified but an ide requires workspace + Location instanceLoc = Platform.getInstanceLocation(); + if (instanceLoc == null) { + MessageDialog + .openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceMandatoryTitle, + IDEWorkbenchMessages.IDEApplication_workspaceMandatoryMessage); + return EXIT_OK; + } + + // -data "/valid/path", workspace already set + // This information is stored in configuration/.settings/org.eclipse.ui.ide.prefs + if (instanceLoc.isSet()) { + // make sure the meta data version is compatible (or the user has + // chosen to overwrite it). + if (!checkValidWorkspace(shell, instanceLoc.getURL())) { + return EXIT_OK; + } + + // at this point its valid, so try to lock it and update the + // metadata version information if successful + try { + if (instanceLoc.lock()) { + writeWorkspaceVersion(); + return null; + } + + // we failed to create the directory. + // Two possibilities: + // 1. directory is already in use + // 2. directory could not be created + File workspaceDirectory = new File(instanceLoc.getURL().getFile()); + if (workspaceDirectory.exists()) { + if (isDevLaunchMode(applicationArguments)) { + return EXIT_WORKSPACE_LOCKED; + } + MessageDialog.openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceCannotLockTitle, + IDEWorkbenchMessages.IDEApplication_workspaceCannotLockMessage); + } else { + MessageDialog.openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); + } + } catch (IOException e) { + IDEWorkbenchPlugin.log("Could not obtain lock for workspace location", //$NON-NLS-1$ + e); + MessageDialog + .openError( + shell, + IDEWorkbenchMessages.InternalError, + e.getMessage()); + } + return EXIT_OK; + } + + // -data @noDefault or -data not specified, prompt and set + ChooseWorkspaceData launchData = null; + if (args.contains(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)) { + launchData = new ChooseWorkspaceData(args.get(SimanticsArguments.DEFAULT_WORKSPACE_LOCATION)); + } else { + launchData = new ChooseWorkspaceData(instanceLoc.getDefault()); + } + + boolean force = args.contains(SimanticsArguments.WORKSPACE_CHOOSER); + boolean suppressAskAgain = args.contains(SimanticsArguments.WORKSPACE_NO_REMEMBER); + + while (true) { + URL workspaceUrl = promptForWorkspace(shell, launchData, force, suppressAskAgain); + if (workspaceUrl == null) { + return EXIT_OK; + } + + // if there is an error with the first selection, then force the + // dialog to open to give the user a chance to correct + force = true; + + try { + // the operation will fail if the url is not a valid + // instance data area, so other checking is unneeded + if (instanceLoc.setURL(workspaceUrl, true)) { + launchData.writePersistedData(); + writeWorkspaceVersion(); + return null; + } + } catch (IllegalStateException e) { + MessageDialog + .openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetTitle, + IDEWorkbenchMessages.IDEApplication_workspaceCannotBeSetMessage); + return EXIT_OK; + } + + // by this point it has been determined that the workspace is + // already in use -- force the user to choose again + MessageDialog.openError(shell, IDEWorkbenchMessages.IDEApplication_workspaceInUseTitle, + IDEWorkbenchMessages.IDEApplication_workspaceInUseMessage); + } + } + + private static boolean isDevLaunchMode(Map args) { + // see org.eclipse.pde.internal.core.PluginPathFinder.isDevLaunchMode() + if (Boolean.getBoolean("eclipse.pde.launch")) //$NON-NLS-1$ + return true; + return args.containsKey("-pdelaunch"); //$NON-NLS-1$ + } + + /** + * Open a workspace selection dialog on the argument shell, populating the + * argument data with the user's selection. Perform first level validation + * on the selection by comparing the version information. This method does + * not examine the runtime state (e.g., is the workspace already locked?). + * + * @param shell + * @param launchData + * @param force + * setting to true makes the dialog open regardless of the + * showDialog value + * @return An URL storing the selected workspace or null if the user has + * canceled the launch operation. + */ + private URL promptForWorkspace(Shell shell, ChooseWorkspaceData launchData, + boolean force, boolean suppressAskAgain) { + URL url = null; + do { + // okay to use the shell now - this is the splash shell + new ChooseWorkspaceDialog(shell, launchData, suppressAskAgain, true).prompt(force); + String instancePath = launchData.getSelection(); + if (instancePath == null) { + return null; + } + + // the dialog is not forced on the first iteration, but is on every + // subsequent one -- if there was an error then the user needs to be + // allowed to fix it + force = true; + + // 70576: don't accept empty input + if (instancePath.length() <= 0) { + MessageDialog + .openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceEmptyTitle, + IDEWorkbenchMessages.IDEApplication_workspaceEmptyMessage); + continue; + } + + // create the workspace if it does not already exist + File workspace = new File(instancePath); + if (!workspace.exists()) { + workspace.mkdir(); + } + + try { + // Don't use File.toURL() since it adds a leading slash that Platform does not + // handle properly. See bug 54081 for more details. + String path = workspace.getAbsolutePath().replace( + File.separatorChar, '/'); + url = new URL("file", null, path); //$NON-NLS-1$ + } catch (MalformedURLException e) { + MessageDialog + .openError( + shell, + IDEWorkbenchMessages.IDEApplication_workspaceInvalidTitle, + IDEWorkbenchMessages.IDEApplication_workspaceInvalidMessage); + continue; + } + } while (!checkValidWorkspace(shell, url)); + + return url; + } + + /** + * Return true if the argument directory is ok to use as a workspace and + * false otherwise. A version check will be performed, and a confirmation + * box may be displayed on the argument shell if an older version is + * detected. + * + * @return true if the argument URL is ok to use as a workspace and false + * otherwise. + */ + private boolean checkValidWorkspace(Shell shell, URL url) { + // a null url is not a valid workspace + if (url == null) { + return false; + } + + String version = readWorkspaceVersion(url); + + // if the version could not be read, then there is not any existing + // workspace data to trample, e.g., perhaps its a new directory that + // is just starting to be used as a workspace + if (version == null) { + return true; + } + + final int ide_version = Integer.parseInt(WORKSPACE_VERSION_VALUE); + int workspace_version = Integer.parseInt(version); + + // equality test is required since any version difference (newer + // or older) may result in data being trampled + if (workspace_version == ide_version) { + return true; + } + + // At this point workspace has been detected to be from a version + // other than the current ide version -- find out if the user wants + // to use it anyhow. + int severity; + String title; + String message; + if (workspace_version < ide_version) { + // Workspace < IDE. Update must be possible without issues, + // so only inform user about it. + severity = MessageDialog.INFORMATION; + title = IDEWorkbenchMessages.IDEApplication_versionTitle_olderWorkspace; + message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_olderWorkspace, url.getFile()); + } else { + // Workspace > IDE. It must have been opened with a newer IDE version. + // Downgrade might be problematic, so warn user about it. + severity = MessageDialog.WARNING; + title = IDEWorkbenchMessages.IDEApplication_versionTitle_newerWorkspace; + message = NLS.bind(IDEWorkbenchMessages.IDEApplication_versionMessage_newerWorkspace, url.getFile()); + } + + MessageBox mbox = new MessageBox(shell, SWT.OK | SWT.CANCEL + | SWT.ICON_WARNING | SWT.APPLICATION_MODAL); + mbox.setText(title); + mbox.setMessage(message); + return mbox.open() == SWT.OK; + } + + /** + * Look at the argument URL for the workspace's version information. Return + * that version if found and null otherwise. + */ + private static String readWorkspaceVersion(URL workspace) { + File versionFile = getVersionFile(workspace, false); + if (versionFile == null || !versionFile.exists()) { + return null; + } + + try { + // Although the version file is not spec'ed to be a Java properties + // file, it happens to follow the same format currently, so using + // Properties to read it is convenient. + Properties props = new Properties(); + FileInputStream is = new FileInputStream(versionFile); + try { + props.load(is); + } finally { + is.close(); + } + + return props.getProperty(WORKSPACE_VERSION_KEY); + } catch (IOException e) { + IDEWorkbenchPlugin.log("Could not read version file", new Status( //$NON-NLS-1$ + IStatus.ERROR, IDEWorkbenchPlugin.IDE_WORKBENCH, + IStatus.ERROR, + e.getMessage() == null ? "" : e.getMessage(), //$NON-NLS-1$, + e)); + return null; + } + } + + /** + * Write the version of the metadata into a known file overwriting any + * existing file contents. Writing the version file isn't really crucial, + * so the function is silent about failure + */ + private static void writeWorkspaceVersion() { + Location instanceLoc = Platform.getInstanceLocation(); + if (instanceLoc == null || instanceLoc.isReadOnly()) { + return; + } + + File versionFile = getVersionFile(instanceLoc.getURL(), true); + if (versionFile == null) { + return; + } + + OutputStream output = null; + try { + String versionLine = WORKSPACE_VERSION_KEY + '=' + + WORKSPACE_VERSION_VALUE; + + output = new FileOutputStream(versionFile); + output.write(versionLine.getBytes("UTF-8")); //$NON-NLS-1$ + } catch (IOException e) { + IDEWorkbenchPlugin.log("Could not write version file", //$NON-NLS-1$ + StatusUtil.newStatus(IStatus.ERROR, e.getMessage(), e)); + } finally { + try { + if (output != null) { + output.close(); + } + } catch (IOException e) { + // do nothing + } + } + } + + /** + * The version file is stored in the metadata area of the workspace. This + * method returns an URL to the file or null if the directory or file does + * not exist (and the create parameter is false). + * + * @param create + * If the directory and file does not exist this parameter + * controls whether it will be created. + * @return An url to the file or null if the version file does not exist or + * could not be created. + */ + private static File getVersionFile(URL workspaceUrl, boolean create) { + if (workspaceUrl == null) { + return null; + } + + try { + // make sure the directory exists + File metaDir = new File(workspaceUrl.getPath(), METADATA_FOLDER); + if (!metaDir.exists() && (!create || !metaDir.mkdir())) { + return null; + } + + // make sure the file exists + File versionFile = new File(metaDir, VERSION_FILENAME); + if (!versionFile.exists() + && (!create || !versionFile.createNewFile())) { + return null; + } + + return versionFile; + } catch (IOException e) { + // cannot log because instance area has not been set + return null; + } + } + + /* (non-Javadoc) + * @see org.eclipse.equinox.app.IApplication#stop() + */ + @Override + public void stop() { + final IWorkbench workbench = PlatformUI.getWorkbench(); + if (workbench == null) + return; + final Display display = workbench.getDisplay(); + display.syncExec(new Runnable() { + @Override + public void run() { + if (!display.isDisposed()) + workbench.close(); + } + }); + } + +}