/*******************************************************************************
* 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.launch;
import static org.grails.ide.eclipse.core.launch.LaunchListenerManager.getLaunchListener;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.StringTokenizer;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.SubProgressMonitor;
import org.eclipse.debug.core.DebugPlugin;
import org.eclipse.debug.core.ILaunch;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.debug.core.ILaunchConfigurationType;
import org.eclipse.debug.core.ILaunchConfigurationWorkingCopy;
import org.eclipse.debug.core.ILaunchManager;
import org.eclipse.jdt.launching.IJavaLaunchConfigurationConstants;
import org.eclipse.jdt.launching.IVMInstall;
import org.eclipse.jdt.launching.IVMRunner;
import org.eclipse.jdt.launching.VMRunnerConfiguration;
import org.grails.ide.eclipse.commands.GrailsCommand;
import org.grails.ide.eclipse.core.GrailsCoreActivator;
import org.grails.ide.eclipse.core.model.GrailsBuildSettingsHelper;
import org.grails.ide.eclipse.core.model.IGrailsInstall;
import org.osgi.framework.Bundle;
import org.osgi.framework.Version;
/**
* LaunchConfigurationDelegate used by the {@link GrailsCommand} class. It
* duplicates the behaviour of its superclass, except for the fact that it
* provides support for the LaunchListener infrastructure (see
* {@link LaunchListenerManager}), and that it doesn't do any automatic
* post-processing of commands (but post-processing can be added externally, via
* {@link LaunchListenerManager})
*
* @author Kris De Volder
* @since 2.5
*/
public class GrailsCommandLaunchConfigurationDelegate extends
GrailsLaunchConfigurationDelegate {
static {
LaunchListenerManager
.promiseSupportForType(getLaunchConfigurationTypeId());
}
public static boolean DEBUG = false;
/**
* This method is a slightly modified copy of the one in the superclass.
* Changes relate to LaunchListenerManager support.
*/
@SuppressWarnings("unchecked")
public void launch(ILaunchConfiguration configuration, String mode,
ILaunch launch, IProgressMonitor monitor) throws CoreException {
IProgressMonitor subMonitor = new SubProgressMonitor(monitor, 5);
checkCancelled(subMonitor);
subMonitor.beginTask("Starting Grails", 5);
subMonitor.worked(1);
checkCancelled(subMonitor);
subMonitor.subTask("Configuring launch parameters...");
// FIXKDV FIXADE Copies of this code exist in
// GrailsLaunchArgumentUtils.prepareClasspath()
// and GrailsCommandLaunchConfigurationDelegate.launch()
// consider refactoring to combine
IVMInstall vm = verifyVMInstall(configuration);
IVMRunner runner = vm.getVMRunner(mode);
if (runner == null) {
runner = vm.getVMRunner(ILaunchManager.RUN_MODE);
}
String projectName = configuration.getAttribute(
IJavaLaunchConfigurationConstants.ATTR_PROJECT_NAME, "");
IProject project = null;
if (!"".equals(projectName)) {
project = ResourcesPlugin.getWorkspace().getRoot()
.getProject(projectName);
}
String grailsHome = GrailsLaunchArgumentUtils
.getGrailsHome(configuration);
configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES,
new HashMap<String, String>()).put("JAVA_HOME",
vm.getInstallLocation().getAbsolutePath());
configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES,
new HashMap<String, String>()).put("GRAILS_HOME", grailsHome);
// ensure that extra line number information is added to GSPs.
configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES,
new HashMap<String, String>()).put(
"GROOVY_PAGE_ADD_LINE_NUMBERS", "true");
String baseDir = configuration.getAttribute(
GrailsLaunchArgumentUtils.PROJECT_DIR_LAUNCH_ATTR, "");
if (baseDir.equals("")) {
baseDir = ResourcesPlugin.getWorkspace().getRoot().getLocation()
.toString();
}
String script = GrailsLaunchConfigurationDelegate
.getScript(configuration);
configuration.getAttribute(ILaunchManager.ATTR_ENVIRONMENT_VARIABLES,
new HashMap<String, String>()).put("GRAILS_HOME", grailsHome);
File workingDir = verifyWorkingDirectory(configuration);
String workingDirName = null;
if (workingDir != null) {
workingDirName = workingDir.getAbsolutePath();
} else {
workingDirName = baseDir;
}
List<String> programArguments = new ArrayList<String>();
String buildListener = GrailsLaunchArgumentUtils
.getGrailsBuildListener(configuration);
List<String> buildListenerClassPathList = GrailsLaunchArgumentUtils
.getBuildListenerClassPath(configuration);
if (buildListenerClassPathList != null
&& !buildListenerClassPathList.isEmpty()) {
// Note: It is important that buildListenerClassPath entries are
// added by using --classpath argument to
// the GrailsStarter. If the entries are added to the boot class
// path or user classpath in the configuration
// itself, it will end up resolving the GrailsBuildListener
// interface to one loaded by a different classloader
// than the one resolved inside of grails itself. This will make
// grails complain that our class does not implement
// that interface (because the interfaces loaded by different
// classloaders are not considered equal).
programArguments.add("--classpath");
programArguments.add(GrailsLaunchArgumentUtils
.toPathsString(buildListenerClassPathList));
}
programArguments.add("--conf");
programArguments.add(grailsHome + "conf" + File.separatorChar
+ "groovy-starter.conf");
programArguments.add("--main");
programArguments
.add("org.codehaus.groovy.grails.cli.GrailsScriptRunner");
StringBuilder grailsCommand = new StringBuilder();
String grailsWorkDir = configuration.getAttribute(
GrailsLaunchArgumentUtils.GRAILS_WORK_DIR_LAUNCH_ATTR, "");
if (!grailsWorkDir.equals("")) {
grailsCommand.append("-Dgrails.work.dir=" + grailsWorkDir + " ");
}
if (buildListener != null) {
grailsCommand.append("-Dgrails.build.listeners=" + buildListener
+ " ");
}
grailsCommand.append(script + " ");
programArguments.add(windowsEscape(grailsCommand.toString().trim()));
List<String> vmArgs = new ArrayList<String>();
if (DEBUG) {
vmArgs.add("-agentlib:jdwp=transport=dt_socket,server=y,address=8123");
}
// add manual configured vm options to the argument list
String existingVmArgs = getVMArguments(configuration);
boolean launchConfHasVMArgs = false;
if (existingVmArgs != null && existingVmArgs.length() > 0) {
launchConfHasVMArgs = true;
StringTokenizer additionalArguments = new StringTokenizer(
existingVmArgs, " ");
while (additionalArguments.hasMoreTokens()) {
vmArgs.add(additionalArguments.nextToken());
}
}
vmArgs.add("-Dbase.dir=" + baseDir);
vmArgs.add("-Dgrails.home=" + grailsHome);
Map<String, String> systemProps = GrailsLaunchArgumentUtils
.getSystemProperties(configuration);
if (systemProps != null) {
for (Map.Entry<String, String> entry : systemProps.entrySet()) {
vmArgs.add("-D" + entry.getKey() + "=" + entry.getValue());
}
}
if (!launchConfHasVMArgs) {
//If the user added their own vmargs to the launch config then the 'default' from global prefs should
// not be used.
GrailsLaunchArgumentUtils.addUserDefinedJVMArgs(vmArgs);
}
GrailsLaunchArgumentUtils.addUserDefinedJVMArgs(vmArgs);
// Grails uses some default memory settings that we want to use as well
// if no others have been configured yet
vmArgs = GrailsLaunchArgumentUtils.addMemorySettings(vmArgs);
String[] envp = getEnvironment(configuration);
Map<String, Object> vmAttributesMap = getVMSpecificAttributesMap(configuration);
String[] classpath = getClasspath(configuration);
String mainTypeName = verifyMainTypeName(configuration);
VMRunnerConfiguration runConfiguration = new VMRunnerConfiguration(
mainTypeName, classpath);
runConfiguration.setProgramArguments(programArguments
.toArray(new String[programArguments.size()]));
runConfiguration
.setVMArguments(vmArgs.toArray(new String[vmArgs.size()]));
runConfiguration.setWorkingDirectory(workingDirName);
runConfiguration.setEnvironment(envp);
runConfiguration.setVMSpecificAttributesMap(vmAttributesMap);
String[] bootpath = getBootpath(configuration);
if (bootpath != null && bootpath.length > 0) {
runConfiguration.setBootClassPath(bootpath);
}
subMonitor.worked(1);
checkCancelled(subMonitor);
subMonitor.subTask("Setting up source locator...");
setDefaultSourceLocator(launch, configuration);
subMonitor.worked(1);
checkCancelled(subMonitor);
subMonitor.worked(1);
checkCancelled(subMonitor);
subMonitor.subTask("Launching Grails...");
try {
GrailsCoreActivator.getDefault().notifyCommandStart(project);
runner.run(runConfiguration, launch, monitor);
AbstractLaunchProcessListener listener = getLaunchListener(configuration);
if (listener != null) {
listener.init(launch.getProcesses()[0]);
}
subMonitor.worked(1);
} catch (Exception e) {
GrailsCoreActivator.log(e);
}
/*
* When I run grails 2.0.2. on the command line it use this to launch
* create app command:
*
* exec /usr/lib/jvm/java-6-sun//bin/java -server -Xmx768M -Xms768M
* -XX:PermSize=256m -XX:MaxPermSize=256m -Dfile.encoding=UTF-8
* -classpath
* /home/kdvolder/Applications/grails-distros/grails-2.0.2.BUILD
* -SNAPSHOT
* /lib/org.codehaus.groovy/groovy-all/1.8.6/jar/groovy-all-1.8.6
* .jar:/home
* /kdvolder/Applications/grails-distros/grails-2.0.2.BUILD-SNAPSHOT
* /dist/grails-bootstrap-2.0.2.BUILD-SNAPSHOT.jar
* -Dgrails.home=/home/kdvolder
* /Applications/grails-distros/grails-2.0.2.BUILD-SNAPSHOT
* -Dtools.jar=/usr/lib/jvm/java-6-sun//lib/tools.jar
* org.codehaus.groovy.grails.cli.support.GrailsStarter --main
* org.codehaus.groovy.grails.cli.GrailsScriptRunner --conf
* /home/kdvolder
* /Applications/grails-distros/grails-2.0.2.BUILD-SNAPSHOT
* /conf/groovy-starter.conf --classpath create-app bork
*/
}
private static String windowsEscape(String argument) {
// There appears to be a bug(?)
// http://bugs.sun.com/view_bug.do?bug_id=6468220
// in Windows ProcessBuilder implementation that incorrectly
// escapes program arguments that contain both spaces and quotes.
if (System.getProperty("os.name").toLowerCase().indexOf("win") >= 0) {
//In Eclipse 4.3 the launching code in Eclipse itself has a similar workaround
// for the bug. If we apply our own fix also this creates problems:
// https://issuetracker.springsource.com/browse/STS-3468
//To avoid we must check the version of the jdt.launching bundle where the
//fix resides. (In this method: org.eclipse.jdt.launching.AbstractVMRunner.quoteWindowsArgs(String[])
Bundle jdtLaunching = Platform.getBundle("org.eclipse.jdt.launching");
if (jdtLaunching!=null) {
Version version = jdtLaunching.getVersion();
//The version of jdtLaunching with 4.3 eclipse is 3.7.0...
Version newEnough = new Version("3.7.0");
if (newEnough.compareTo(version)<=0) {
//new enough means Eclipse already has the fix.
//MUST NOT apply our own fix
} else {
//Eclipse 4.2 or 3.7 doesn't have fix so apply ours.
return winQuote(argument);
}
}
}
return argument;
}
static boolean needsQuoting(String s) {
int len = s.length();
if (len == 0) // empty string have to be quoted
return true;
for (int i = 0; i < len; i++) {
switch (s.charAt(i)) {
case ' ':
case '\t':
case '\\':
case '"':
return true;
}
}
return false;
}
static String winQuote(String s) {
if (!needsQuoting(s))
return s;
s = s.replaceAll("([\\\\]*)\"", "$1$1\\\\\"");
s = s.replaceAll("([\\\\]*)\\z", "$1$1");
return "\"" + s + "\"";
}
public static ILaunchConfigurationWorkingCopy getLaunchConfiguration(
IGrailsInstall install, IProject project, String script,
String baseDirectory) throws CoreException {
ILaunchConfigurationType configType = DebugPlugin.getDefault()
.getLaunchManager()
.getLaunchConfigurationType(getLaunchConfigurationTypeId());
if (install == null) {
install = GrailsCoreActivator.getDefault().getInstallManager()
.getGrailsInstall(project);
if (install == null) {
return null;
}
}
if (baseDirectory == null) {
if (project != null) {
baseDirectory = GrailsBuildSettingsHelper.getBaseDir(project);
}
// If not set, launch will use workspace as baseDir
}
String nameAndScript = (script != null ? "(" + script + ")" : "");
if (project != null) {
nameAndScript = project.getName() + " " + nameAndScript;
}
nameAndScript = sanitize(nameAndScript);
ILaunchConfigurationWorkingCopy wc = configType.newInstance(null,
nameAndScript);
GrailsLaunchArgumentUtils.prepareLaunchConfiguration(project, script,
install, baseDirectory, wc);
// System.out.println("===================");
// Map attribs = wc.getAttributes();
// for (Object k : attribs.keySet()) {
// System.out.println(k +" = "+attribs.get(k));
// }
// System.out.println("===================");
return wc;
}
private static String getLaunchConfigurationTypeId() {
return "org.grails.ide.eclipse.core.launchCommandConfig";
}
@Override
public boolean finalLaunchCheck(ILaunchConfiguration configuration,
String mode, IProgressMonitor monitor) throws CoreException {
// We don't do the one from super (which checks for build errors).
// Grails command does its own compiling and checking if it needs to.
return true;
}
}