/******************************************************************************* * 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; import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.concurrent.CopyOnWriteArrayList; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IProject; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceChangeEvent; import org.eclipse.core.resources.IResourceChangeListener; import org.eclipse.core.resources.IResourceDelta; import org.eclipse.core.resources.IResourceDeltaVisitor; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IStatus; import org.eclipse.core.runtime.Platform; import org.eclipse.core.runtime.Plugin; import org.eclipse.core.runtime.Status; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.InstanceScope; import org.grails.ide.eclipse.commands.GrailsCommand; import org.grails.ide.eclipse.commands.GrailsExecutor; import org.grails.ide.eclipse.core.internal.classpath.GrailsClasspathUtils; import org.grails.ide.eclipse.core.internal.plugins.GrailsCore; import org.grails.ide.eclipse.core.junit.GrailsTestKindRegistry; import org.grails.ide.eclipse.core.launch.GrailsLaunchArgumentUtils; import org.grails.ide.eclipse.core.model.GrailsInstallManager; import org.grails.ide.eclipse.core.model.IGrailsCommandListener; import org.grails.ide.eclipse.core.model.IGrailsCommandResourceChangeListener; import org.grails.ide.eclipse.longrunning.LongRunningProcessGrailsExecutor; import org.osgi.framework.Bundle; import org.osgi.framework.BundleContext; import org.osgi.service.prefs.BackingStoreException; import org.springsource.ide.eclipse.commons.core.FileUtil; /** * @author Christian Dupuis * @author Andy Clement * @author Andrew Eisenberg * @author Nieraj Singh * @author Kris De Volder * @since 2.2.0 */ public class GrailsCoreActivator extends Plugin { private class GroovyResourceChangeListener implements IResourceChangeListener { protected class GroovyResourceVisitor implements IResourceDeltaVisitor { protected boolean resourceAdded(IResource resource) { if (resource instanceof IFile) { for (IGrailsCommandResourceChangeListener listener : commandResourceListeners) { if (listener.supports(resource.getProject())) { listener.newResource(resource); } } } return true; } protected boolean resourceChanged(IResource resource, int flags) { if (resource instanceof IFile) { for (IGrailsCommandResourceChangeListener listener : commandResourceListeners) { if (listener.supports(resource.getProject())) { listener.changedResource(resource); } } } return true; } public final boolean visit(IResourceDelta delta) throws CoreException { IResource resource = delta.getResource(); if (resource != null) { switch (delta.getKind()) { case IResourceDelta.ADDED: return resourceAdded(resource); case IResourceDelta.CHANGED: return resourceChanged(resource, delta.getFlags()); } } return false; } } private static final int VISITOR_FLAGS = IResourceDelta.ADDED | IResourceDelta.CHANGED; protected IResourceDeltaVisitor getVisitor() { return new GroovyResourceVisitor(); } public void resourceChanged(IResourceChangeEvent event) { if (event.getSource() instanceof IWorkspace) { int eventType = event.getType(); switch (eventType) { case IResourceChangeEvent.POST_CHANGE: IResourceDelta delta = event.getDelta(); if (delta != null) { try { delta.accept(getVisitor(), VISITOR_FLAGS); } catch (CoreException e) { log("Error while traversing resource change delta", e); } } break; } } else if (event.getSource() instanceof IProject) { int eventType = event.getType(); switch (eventType) { case IResourceChangeEvent.POST_CHANGE: IResourceDelta delta = event.getDelta(); if (delta != null) { try { delta.accept(getVisitor(), VISITOR_FLAGS); } catch (CoreException e) { log("Error while traversing resource change delta", e); } } break; } } } } // The plug-in ID public static final String PLUGIN_ID = "org.grails.ide.eclipse.core"; public static final String OLD_PLUGIN_ID = "com.springsource.sts.grails.core"; private static GrailsCoreActivator plugin; //public static final String GRAILS_INSTALL_PROPERTY = PLUGIN_ID + ".install.name"; public static final String GRAILS_LAUNCH_SYSTEM_PROPERTIES = PLUGIN_ID + ".launch.properties"; /** The identifier for enablement of project versus workspace settings */ //public static final String PROJECT_PROPERTY_ID = "use.default.install"; private static final String GRAILS_COMMAND_TIMEOUT_PREFERENCE = PLUGIN_ID + ".COMMAND_TIMEOUT"; public static final String PATH_VARIABLE_NAME = "GRAILS_ROOT"; private static final String KEEP_RUNNING_PREFERENCE = PLUGIN_ID + ".KEEP_RUNNING"; private static final String JVM_ARGS_PREFERENCE = PLUGIN_ID + ".JVM_ARGS"; public static final boolean DEFAULT_KEEP_RUNNING_PREFERENCE = true; private static final String GRAILS_COMMAND_OUTPUT_LIMIT_PREFERENCE = PLUGIN_ID+".OUTPUT_LIMIT"; private static final int DEFAULT_GRAILS_COMMAND_OUTPUT_LIMIT_PREFERENCE = 200000;; private static final String CLEAN_OUTPUT_PREFERENCE = PLUGIN_ID+".cleanOutput"; public static final boolean DEFAULT_CLEAN_OUTPUT_PREFERENCE = true; public static IStatus createErrorStatus(String message, Throwable exception) { if (message == null) { message = ""; } return new Status(IStatus.ERROR, PLUGIN_ID, 0, message, exception); } public static IStatus createWarningStatus(String message, Throwable exception) { if (message == null) { message = ""; } return new Status(IStatus.WARNING, PLUGIN_ID, 0, message, exception); } public static GrailsCoreActivator getDefault() { return plugin; } public static void log(IStatus status) { if (logger == null) { getDefault().getLog().log(status); } else { logger.logEntry(status); } } public static void log(String string) { try { throw new Error(string); } catch (Error e) { log(e); } } public static void log(String message, Throwable exception) { IStatus status = createErrorStatus(message, exception); log(status); } public static void log(Throwable exception) { log(createErrorStatus("Internal Error", exception)); } public static void logWarning(String message, Throwable exception) { log(GrailsCoreActivator.createWarningStatus(message, exception)); } public static void setLogger(ILogger logger) { GrailsCoreActivator.logger = logger; } public static void testMode(boolean onBuildSite) { // test flag used by Grails stuff to know if we are in test mode. testMode = true; useFakeUserHome = true; isOnBuildSite = onBuildSite; } public static void testMode(boolean onBuildSite, boolean fakeUserHome) { // test flag used by Grails stuff to know if we are in test mode. testMode = true; isOnBuildSite = onBuildSite; useFakeUserHome = fakeUserHome; } public static void trace(String message) { if (logger != null) { logger.logEntry(new Status(IStatus.OK, PLUGIN_ID, message)); } } private GrailsInstallManager installManager; private GroovyResourceChangeListener resourceChangeListener = new GroovyResourceChangeListener(); private List<IGrailsCommandResourceChangeListener> commandResourceListeners = new CopyOnWriteArrayList<IGrailsCommandResourceChangeListener>(); private List<IGrailsCommandListener> commandListeners = new CopyOnWriteArrayList<IGrailsCommandListener>(); private static ILogger logger; public static boolean testMode = false; // Set to true while running 'core' tests. public static boolean isOnBuildSite; // Should be set to true when running on the build site /** * During testing, we use a 'fake' home for grails command execution to avoid state sharing with concurrent test builds. */ private File fakeUserHome; private static boolean useFakeUserHome = false; public static final String GRAILS_RESOURCES_PLUGIN_ID = "org.grails.ide.eclipse.resources"; public static final String DEFAULT_JVM_ARGS_PREFERENCE = null; public void addGrailsCommandListener(IGrailsCommandListener listener) { commandListeners.add(listener); } public void addGrailsCommandResourceListener(IGrailsCommandResourceChangeListener listener) { commandResourceListeners.add(listener); } /** * Replace all '\u001D' (group separator) with '=' * and '\u001E' (unit separator) with ',' * * @param key * @return */ private String decode(String key) { key = key.replace('\u001D', '='); key = key.replace('\u001E', ','); return key; } /** * Replace all '=' with '\u001D' (group separator) * and ',' with '\u001E' (unit separator) * * @param key * @return */ private String encode(String key) { key = key.replace('=', '\u001D'); key = key.replace(',', '\u001E'); return key; } public boolean getCleanOutput() { return getPreferences().getBoolean(CLEAN_OUTPUT_PREFERENCE, DEFAULT_CLEAN_OUTPUT_PREFERENCE); } /** * @return The upper bound, in number of characters of Grails command output that is kept for any * grails command. */ public int getGrailsCommandOutputLimit() { return getPreferences().getInt(GRAILS_COMMAND_OUTPUT_LIMIT_PREFERENCE, DEFAULT_GRAILS_COMMAND_OUTPUT_LIMIT_PREFERENCE); } public int getGrailsCommandTimeOut() { return getPreferences().getInt(GRAILS_COMMAND_TIMEOUT_PREFERENCE, GrailsCommand.DEFAULT_TIMEOUT); } public GrailsInstallManager getInstallManager() { if (installManager==null) { GrailsInstallManager newManager = new GrailsInstallManager(); newManager.start(); installManager = newManager; } return installManager; } public boolean getKeepRunning() { return getPreferences().getBoolean(KEEP_RUNNING_PREFERENCE, DEFAULT_KEEP_RUNNING_PREFERENCE); } public String getJVMArgs() { return getPreferences().get(JVM_ARGS_PREFERENCE, DEFAULT_JVM_ARGS_PREFERENCE); } /** * Get system properties that should be passed to any Grails command. This includes all the * user supplied props plus some additional ones that may need to be added automatically. */ public Map<String, String> getLaunchSystemProperties() { Map<String, String> props = getUserSupliedLaunchSystemProperties(); if (GrailsCoreActivator.testMode) { GrailsLaunchArgumentUtils.addProxyProperties(props); try { props.put("user.home", getUserHome().getCanonicalPath()); } catch (IOException e) { GrailsCoreActivator.log(e); } } //if (isWindows()) {//STS-2552: jline UnixTerminal causes grails and STS to be 'suspended' on Linux systems. // This is because Unix job control will suspend background jobs that try to access the terminal. // So to avoid this problem we now do this for any OS not just Windows. //If we don't set this property on windows then Jline will //instantiate "WindowsConsole" based on "os.name" property, //but the "WindowsConsole doesn't work well when inside //the Eclipse console UI. if (!props.containsKey("jline.terminal")) { // don't overwrite if user defined it. props.put("jline.terminal", "jline.UnsupportedTerminal"); } //} return props; } private IEclipsePreferences getPreferences() { return InstanceScope.INSTANCE.getNode(PLUGIN_ID); } /** * Get the 'fake' 'user.home' used by grails commands during testing. */ public File getUserHome() { if (!useFakeUserHome) { return new File(System.getProperty("user.home")); } else { try { if (fakeUserHome==null) { fakeUserHome = FileUtil.createTempDirectory("fakeHome"); } } catch (IOException e) { fakeUserHome = new File(System.getProperty("user.home")); } return fakeUserHome; } } /** * Get the system properties required for a launching of a grails command, as set * by the user on the Grails >> Launch preferences page. * <p> * Note additional properties may be set automatically by Grails, this map contains * only the ones specified by the user. * * @return Map of user supplied system properties to be set on any grails command's execution */ public Map<String, String> getUserSupliedLaunchSystemProperties() { String str = getPreferences().get(GRAILS_LAUNCH_SYSTEM_PROPERTIES, ""); String[] props = str.split(","); Map<String, String> propsMap = new HashMap<String, String>(props.length*2); for (String prop : props) { String[] nameValue = prop.split("="); if (nameValue.length == 2) { propsMap.put(decode(nameValue[0]), decode(nameValue[1])); } } return propsMap; } private boolean isWindows() { String os = System.getProperty("os.name").toLowerCase(); return os.contains("windows"); } public void notifyCommandFinish(IProject project) { if (project != null) { for (IGrailsCommandResourceChangeListener listener : commandResourceListeners) { if (listener.supports(project)) { listener.finish(); } } for (IGrailsCommandListener listener : commandListeners) { if (listener.supports(project)) { listener.finish(); } } } } public void notifyCommandStart(IProject project) { if (project != null) { for (IGrailsCommandResourceChangeListener listener : commandResourceListeners) { if (listener.supports(project)) { listener.start(); } } for (IGrailsCommandListener listener : commandListeners) { if (listener.supports(project)) { listener.start(); } } } } private void putStringPref(String key, String value) { IEclipsePreferences prefs = getPreferences(); if (value==null) { prefs.remove(key); } else { prefs.put(key, value); } try { prefs.flush(); } catch (BackingStoreException e) { GrailsCoreActivator.log(e); } } private void putBooleanPref(String key, boolean value) { IEclipsePreferences prefs = getPreferences(); prefs.putBoolean(key, value); try { prefs.flush(); } catch (BackingStoreException e) { GrailsCoreActivator.log(e); } } private void putIntPref(String key, int value) { IEclipsePreferences prefs = getPreferences(); prefs.putInt(key, value); try { prefs.flush(); } catch (BackingStoreException e) { GrailsCoreActivator.log(e); } } public void removeGrailsCommandListener(IGrailsCommandListener listener) { commandListeners.remove(listener); } public void removeGrailsCommandResourceListener(IGrailsCommandResourceChangeListener listener) { commandResourceListeners.remove(listener); } public void setCleanOutput(boolean cleanOutput) { putBooleanPref(CLEAN_OUTPUT_PREFERENCE, cleanOutput); } public void setGrailsCommandOutputLimit(int maxCharacterCount) { Assert.isLegal(maxCharacterCount>0); int orgValue = getGrailsCommandOutputLimit(); if (orgValue!=maxCharacterCount) { putIntPref(GRAILS_COMMAND_OUTPUT_LIMIT_PREFERENCE, maxCharacterCount); } } public void setGrailsCommandTimeOut(int timeOutValue) { putIntPref(GRAILS_COMMAND_TIMEOUT_PREFERENCE, timeOutValue); } public void setKeepGrailsRunning(boolean newValue) { boolean orgValue = getKeepRunning(); if (newValue!=orgValue) { putBooleanPref(KEEP_RUNNING_PREFERENCE, newValue); GrailsExecutor.shutDownIfNeeded(); //force new executor creation next time } } public void setJVMArgs(String newValue) { String orgValue = getJVMArgs(); if (!equals(newValue, orgValue)) { putStringPref(JVM_ARGS_PREFERENCE, newValue); GrailsExecutor.shutDownIfNeeded(); //force new executor creation next time } } private boolean equals(String a, String b) { if (a==null) { return a==b; } else { return a.equals(b); } } /** * Set the system properties required for launching of a grails command, as set * by the user on the Grails >> Launch preferences page. */ public void setUserSupliedLaunchSystemProperties(Map<String, String> props) { StringBuilder sb = new StringBuilder(); for (Entry<String,String> entry : props.entrySet()) { sb.append(encode(entry.getKey()) + "=" + encode(entry.getValue()) + ","); } // remove trailing , if (sb.length() > 0) { sb.replace(sb.length()-1, sb.length(), ""); } IEclipsePreferences node = getPreferences(); node.put(GRAILS_LAUNCH_SYSTEM_PROPERTIES, sb.toString()); try { node.flush(); } catch (BackingStoreException e) { log(e); } // For system properties to propagate to external process, it must be restarted. LongRunningProcessGrailsExecutor.shutDownIfNeeded(); } /// Testing mode code only.... public void start(BundleContext context) throws Exception { super.start(context); plugin = this; GrailsCore.get().initialize(); ResourcesPlugin.getWorkspace().addResourceChangeListener(resourceChangeListener); GrailsClasspathUtils.createPathVariableIfRequired(); Bundle refactoringBundle = Platform.getBundle("org.grails.ide.eclipse.refactoring"); if (refactoringBundle!=null) { refactoringBundle.start(); } GrailsTestKindRegistry.initialize(); } public void stop(BundleContext context) throws Exception { GrailsExecutor.shutDownIfNeeded(); ResourcesPlugin.getWorkspace().removeResourceChangeListener(resourceChangeListener); plugin = null; GrailsCore.get().dispose(); super.stop(context); } }