/******************************************************************************* * Copyright (c) 2012 Pivotal Software, Inc. * 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: * Pivotal Software, Inc. - initial API and implementation *******************************************************************************/ package org.grails.ide.eclipse.core.internal.classpath; import java.io.BufferedOutputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.Properties; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.MultiStatus; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Status; import org.eclipse.jdt.core.JavaCore; import org.grails.ide.eclipse.commands.GrailsCommandUtils; import org.grails.ide.eclipse.core.GrailsCoreActivator; import org.springsource.ide.eclipse.commons.frameworks.core.internal.plugins.PluginVersion; /** * A helper class, with some local state to help installing and uninstalling plugins and reverting operation * if errors occur during the (un)installation process. * @author Kris De Volder * @author Martin Lippert * @author Andrew Eisenberg */ public class FastGrailsPluginInstaller { //IMPORTANT: // This implements a 'fast' version of installing / uninstalling plugins. But unfortunately this fast version // isn't always reliable. // See // - https://issuetracker.springsource.com/browse/STS-1506 // - https://issuetracker.springsource.com/browse/STS-1502 //I'm keeping it around for now, for reference, but ultimately I don't think we want // to use this solution unless we have really good tests for it. Otherwise we should 'play it safe' and // rely on the (un)install-plugin commands provided by Grails to install plugins. private static final String APPLICATION_PROPERTIES = "application.properties"; private IProject project; private Collection<PluginVersion> uninstallPlugins; private Collection<PluginVersion> installPlugins; private Properties savedProps = null; public static IStatus performPluginChanges( Collection<PluginVersion> selectedUninstallPlugins, Collection<PluginVersion> selectedInstallPlugins, IProject project, IProgressMonitor monitor) { return new FastGrailsPluginInstaller(selectedUninstallPlugins, selectedInstallPlugins, project).performChanges(monitor); } private FastGrailsPluginInstaller( Collection<PluginVersion> selectedUninstallPlugins, Collection<PluginVersion> selectedInstallPlugins, IProject project) { if (selectedInstallPlugins==null) { selectedInstallPlugins = new ArrayList<PluginVersion>(); } if (selectedUninstallPlugins==null) { selectedUninstallPlugins = new ArrayList<PluginVersion>(); } this.uninstallPlugins = selectedUninstallPlugins; this.installPlugins = selectedInstallPlugins; this.project = project; } private IStatus performChanges(IProgressMonitor monitor) { if (monitor == null) { monitor = new NullProgressMonitor(); } if (!GrailsClasspathUtils.hasClasspathContainer(JavaCore.create(project))) { return new Status(IStatus.ERROR, GrailsCoreActivator.PLUGIN_ID, "Plugin Management requires that Grails Dependency Management is enabled for project '"+project.getName()+"'"); } monitor.beginTask("Install and uninstall grails plugins", 3); try { monitor.subTask("Updating application.properties and BuildConfig.groovy"); IStatus result = internalPerformPluginChanges(project, uninstallPlugins, installPlugins); if (result.getSeverity() > IStatus.WARNING) { return result; } monitor.worked(1); try { monitor.subTask("Installing / Uninstalling plugins"); GrailsCommandUtils.refreshDependencies(JavaCore.create(project), true); monitor.worked(1); monitor.subTask("Refreshing project dependencies"); GrailsCommandUtils.refreshDependencies(JavaCore.create(project), true); } catch (CoreException e) { result = new Status(IStatus.ERROR, GrailsCoreActivator.PLUGIN_ID, "Exception thrown when trying to update 'application.properties'", e); GrailsCoreActivator.log(result); boolean revert = askRevert(); if (revert) { monitor.subTask("An error occurred ==> Reverting operation"); revert(); } } monitor.worked(1); return result; } finally { monitor.done(); } } private void revert() { Assert.isTrue(savedProps!=null, "Can't revert 'application.properties': no saved state"); OutputStream out = null; try { IFile file = project.getFile(APPLICATION_PROPERTIES); out = new BufferedOutputStream(new FileOutputStream(file.getLocation().toFile())); savedProps.store(out, createComment()); GrailsCommandUtils.refreshDependencies(JavaCore.create(project), true); } catch (Exception e) { //Swallow, already in error recovery mode, don't spam any more error messages. e.printStackTrace(); } finally { if (out!=null) { try { out.close(); } catch (IOException e) { //Swallow, already in error recovery mode, don't spam any more error messages. } } } } /** * When an error occurs during refresh dependecies (which install / uninstalls plugins) we ask the user if they want to revert * the application properties file to its original state or leave the project in its probably broken state. */ private boolean askRevert() { return true; // Can't do this here, not in UI thread and also this plugin is a 'core' plugin. // return MessageDialog.openQuestion(null, "An error has occurred updating 'application.properties' for '"+ project.getName()+"'. See error log for details." , // "Do you want to revert 'application.properties' to its original state? (Recomended)"); } private static String createComment() { return "Grails Metadata file"; } /** * Makes modifications to application.properties to reflect installed / uninstalled plugins. Also stores a copy of the original properties * in 'savedProps' to be able to revert any changes in case of an error. */ private IStatus internalPerformPluginChanges(IProject project, Collection<PluginVersion> selectedUninstallPlugins, Collection<PluginVersion> selectedInstallPlugins) { InputStream in = null; OutputStream out = null; List<IStatus> statuses = new ArrayList<IStatus>(2); try { Properties props = new Properties(); IFile file = project.getFile(APPLICATION_PROPERTIES); props.load(in=file.getContents()); savedProps = (Properties) props.clone(); for (PluginVersion toUninstall : selectedUninstallPlugins) { if (toUninstall.getParent().isInPlace()) { // change BuildConfig.groovy script IProject uninstallProject = ResourcesPlugin.getWorkspace().getRoot().getProject(toUninstall.getName()); if (uninstallProject.isAccessible()) { IStatus result = GrailsPluginUtil.removePluginDependency(project, uninstallProject); if (!result.isOK()) { statuses.add(result); } } else { // add warning status String reason; if (uninstallProject.exists()) { reason = " is closed. "; } else { reason = " does not exist. "; } statuses.add(new Status(IStatus.WARNING, GrailsCoreActivator.PLUGIN_ID, "Project " + uninstallProject.getName() + reason + "Could not uninstall dependency. Try manually editing the BuildConfig.groovy file.")); } } else { // change application.properties props.remove("plugins." + toUninstall.getName()); } } for (PluginVersion toInstall : selectedInstallPlugins) { if (toInstall.getParent().isInPlace()) { // change BuildConfig.groovy script IProject installProject = ResourcesPlugin.getWorkspace().getRoot().getProject(toInstall.getName()); if (installProject.isAccessible()) { IStatus result = GrailsPluginUtil.addPluginDependency(project, installProject); if (!result.isOK()) { statuses.add(result); } } else { // add warning status String reason; if (installProject.exists()) { reason = " is closed. "; } else { reason = " does not exist. "; } statuses.add(new Status(IStatus.WARNING, GrailsCoreActivator.PLUGIN_ID, "Project " + installProject.getName() + reason + "Could not install dependency. Try manually editing the BuildConfig.groovy file.")); } } else { // change application.properties props.put("plugins." + toInstall.getName(), toInstall.getVersion()); } } out = new FileOutputStream(project.getFile(APPLICATION_PROPERTIES).getLocation().toFile()); props.store(out, createComment()); file.refreshLocal(IResource.DEPTH_ZERO, null); } catch (Exception e) { Status status = new Status(IStatus.ERROR, GrailsCoreActivator.PLUGIN_ID, "Exception thrown when trying to update 'application.properties'", e); return status; } finally { try { if (in!=null) { in.close(); } if (out!=null) { out.close(); } } catch (IOException e) { } } if (statuses.size() == 0) { return Status.OK_STATUS; } else { return new MultiStatus(GrailsCoreActivator.PLUGIN_ID, -1, statuses.toArray(new IStatus[0]), "Warnings occurred during plugin changes operation", null); } } }