blob: c59772c15e8d8457260447fa7aca4e89320390c8 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2001, 2006 IBM Corporation and others.
* 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:
* IBM Corporation - initial API and implementation
*******************************************************************************/
/*
* $RCSfile: LocalProxyLaunchDelegate.java,v $ $Revision: 1.35 $ $Date: 2006/05/23 15:43:03 $
*/
package org.eclipse.jem.internal.proxy.remote;
import java.io.File;
import java.io.IOException;
import java.net.*;
import java.text.MessageFormat;
import java.util.*;
import java.util.logging.Level;
import org.eclipse.core.runtime.*;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.debug.core.*;
import org.eclipse.debug.core.model.*;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.launching.*;
import org.eclipse.jem.internal.proxy.common.remote.ExpressionCommands;
import org.eclipse.jem.internal.proxy.core.*;
import org.eclipse.jem.internal.proxy.remote.awt.REMRegisterAWT;
import org.eclipse.jem.util.TimerTests;
import org.eclipse.jem.util.logger.proxy.Logger;
/**
* Launch Delegate for launching Local (i.e. remote vm is on local system). Here "remote" means the
* registry is not in the IDE but in a separate VM, and "local" means that is in on the local
* physical machine and not on a separate machine.
*
* @since 1.0.0
*/
public class LocalProxyLaunchDelegate extends AbstractJavaLaunchConfigurationDelegate {
/*
* (non-Javadoc)
*
* @see org.eclipse.debug.core.model.ILaunchConfigurationDelegate#launch(org.eclipse.debug.core.ILaunchConfiguration,
* java.lang.String, org.eclipse.debug.core.ILaunch,
* org.eclipse.core.runtime.IProgressMonitor)
*/
public void launch(ILaunchConfiguration configuration, String mode, ILaunch launch, IProgressMonitor pm) throws CoreException {
String launchKey = configuration.getAttribute(IProxyConstants.ATTRIBUTE_LAUNCH_KEY, (String) null);
if (launchKey == null)
abort(ProxyRemoteMessages.ProxyRemoteNoLaunchKey, null, 0);
// In Eclipse, even if private, a launch will show up in the debug process tree and in the console viewer.
// To be absolutely private, we need to remove the launch which has already been added.
if (ProxyLaunchSupport.ATTR_PRIVATE != null && configuration.getAttribute(ProxyLaunchSupport.ATTR_PRIVATE, false))
DebugPlugin.getDefault().getLaunchManager().removeLaunch(launch);
if (pm == null) {
pm = new NullProgressMonitor();
}
IJavaProject project = getJavaProject(configuration);
String name = configuration.getAttribute(IProxyConstants.ATTRIBUTE_VM_TITLE, (String) null);
if (name == null)
name = MessageFormat.format(ProxyRemoteMessages.ProxyRemoteVMName, new Object[] { project != null ? project.getProject().getName() : "" }); //$NON-NLS-1$
else
name = MessageFormat.format(ProxyRemoteMessages.ProxyRemoteVMNameWithComment, new Object[] { project != null ? project.getProject().getName() : "", name }); //$NON-NLS-1$
String stepId = "Launch VM ( " + name + " )"; //$NON-NLS-1$ //$NON-NLS-2$
TimerTests.basicTest.startStep(stepId);
// Problem with launch, can't have double-quotes in vmName.
if (name.indexOf('"') != -1)
name = name.replace('"', '\'');
pm.beginTask("", 500); //$NON-NLS-1$
pm.subTask(MessageFormat.format(ProxyRemoteMessages.ProxyRemoteLaunchVM, new Object[] { name }));
// check for cancellation
if (pm.isCanceled())
return;
IVMInstall vm = verifyVMInstall(configuration);
IVMRunner runner = vm.getVMRunner(mode);
if (runner == null) {
abort(MessageFormat.format(ProxyRemoteMessages.Proxy_NoRunner_ERROR_, new Object[] { name }), null, 0);
}
File workingDir = verifyWorkingDirectory(configuration);
String workingDirName = null;
if (workingDir != null) {
workingDirName = workingDir.getAbsolutePath();
}
// Environment variables
String[] envp = DebugPlugin.getDefault().getLaunchManager().getEnvironment(configuration);
// Program & VM args
String pgmArgs = getProgramArguments(configuration);
String vmArgs = getVMArguments(configuration);
ExecutionArguments execArgs = new ExecutionArguments(vmArgs, pgmArgs);
// VM-specific attributes
Map vmAttributesMap = getVMSpecificAttributesMap(configuration);
pm.worked(100);
// Now let's get the classpaths created through the contributors.
URL[] classpath = ProxyLaunchSupport.convertStringPathsToURL(getClasspath(configuration));
String[][] bootpathInfoStrings = getBootpathExt(vmAttributesMap);
URL[][] bootpathInfo = new URL[][]{
ProxyLaunchSupport.convertStringPathsToURL(bootpathInfoStrings[0]),
ProxyLaunchSupport.convertStringPathsToURL(bootpathInfoStrings[1]),
ProxyLaunchSupport.convertStringPathsToURL(bootpathInfoStrings[2]),
};
ProxyLaunchSupport.LaunchInfo launchInfo = ProxyLaunchSupport.getInfo(launchKey);
final IConfigurationContributor[] contributors = launchInfo.contributors;
final LocalFileConfigurationContributorController controller =
new LocalFileConfigurationContributorController(classpath, bootpathInfo, launchInfo);
if (contributors != null) {
for (int i = 0; i < contributors.length; i++) {
// Run in safe mode so that anything happens we don't go away.
final int ii = i;
SafeRunner.run(new ISafeRunnable() {
public void handleException(Throwable exception) {
// Don't need to do anything. Platform.run logs it for me.
}
public void run() throws Exception {
contributors[ii].contributeClasspaths(controller);
}
});
}
}
// Add in the required ones by the Proxy support. These are hard-coded since they are
// required.
ProxyRemoteUtil.updateClassPaths(controller);
addInFragmentLibraries(controller, launchInfo.getConfigInfo());
classpath = controller.getFinalClasspath();
if (bootpathInfo[0] != controller.getFinalPrependBootpath()) {
if (vmAttributesMap == null)
vmAttributesMap = new HashMap(2);
vmAttributesMap.put(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_PREPEND, ProxyLaunchSupport.convertURLsToStrings(bootpathInfo[0]));
}
if (bootpathInfo[2] != controller.getFinalAppendBootpath()) {
if (vmAttributesMap == null)
vmAttributesMap = new HashMap(2);
vmAttributesMap.put(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_APPEND, ProxyLaunchSupport.convertURLsToStrings(bootpathInfo[2]));
}
// check for cancellation
if (pm.isCanceled())
return;
pm.worked(100);
// Create VM config
VMRunnerConfiguration runConfig =
new VMRunnerConfiguration("org.eclipse.jem.internal.proxy.vm.remote.RemoteVMApplication", ProxyLaunchSupport.convertURLsToStrings(classpath)); //$NON-NLS-1$
REMProxyFactoryRegistry registry = new REMProxyFactoryRegistry(ProxyRemoteUtil.getRegistryController(), name);
Integer registryKey = registry.getRegistryKey();
Integer bufSize = Integer.getInteger("proxyvm.bufsize"); //$NON-NLS-1$
if (bufSize == null)
bufSize = new Integer(16000);
int masterServerPort = ProxyRemoteUtil.getRegistryController().getMasterSocketPort();
// See if debug mode is requested.
DebugModeHelper dh = new DebugModeHelper();
boolean debugMode = dh.debugMode(name);
boolean useNoverify = ProxyPlugin.getPlugin().getPluginPreferences().getBoolean(ProxyPlugin.PREFERENCES_VM_NOVERIFY_KEY);
String[] evmArgs = execArgs.getVMArgumentsArray();
int extraArgs = 4; // Number of extra standard args added (if number changes below, this must change)
if (debugMode)
extraArgs+=4; // Number of extra args added for debug mode (if number changes below, this must change).
if(useNoverify)
extraArgs++; // An extra arg added for '-noverify' flag (if number changes below, this must change).
boolean useExpressionTracing = "true".equalsIgnoreCase(Platform.getDebugOption(ProxyPlugin.getPlugin().getBundle().getSymbolicName() + ProxyLaunchSupport.EXPRESSION_TRACING)); //$NON-NLS-1$
long expressionTracingThreshold = -1;
if (useExpressionTracing) {
extraArgs++;
String thresholdString = Platform.getDebugOption(ProxyPlugin.getPlugin().getBundle().getSymbolicName() + ProxyLaunchSupport.EXPRESSION_TRACEING_TIMER_THRESHOLD);
if (thresholdString != null) {
try {
expressionTracingThreshold = Long.valueOf(thresholdString).longValue();
extraArgs++;
} catch (NumberFormatException e) {
}
}
}
List javaLibPaths = controller.getFinalJavaLibraryPath();
int existingLibpaths = -1;
if (!javaLibPaths.isEmpty()) {
// first need to see if java lib path also specified in standard args by someone configuring the configuration by hand.
for (int i = 0; i < evmArgs.length; i++) {
if (evmArgs[i].startsWith("-Djava.library.path")) { //$NON-NLS-1$
// We found one already here, save the spot so we update it later.
existingLibpaths = i;
break;
}
}
if (existingLibpaths == -1)
++extraArgs; // Need to have room for one more.
}
String[] cvmArgs = new String[evmArgs.length + extraArgs];
System.arraycopy(evmArgs, 0, cvmArgs, extraArgs, evmArgs.length); // Put existing into new list at the end.
int cvmArgsCount=0;
cvmArgs[cvmArgsCount++] = "-Dproxyvm.registryKey=" + registryKey; //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Dproxyvm.masterPort=" + String.valueOf(masterServerPort); //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Dproxyvm.bufsize=" + bufSize; //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Dproxyvm.servername=" + name; //$NON-NLS-1$
if(useNoverify)
cvmArgs[cvmArgsCount++] = "-noverify"; //$NON-NLS-1$
if (useExpressionTracing) {
cvmArgs[cvmArgsCount++] = "-D"+ExpressionCommands.EXPRESSIONTRACE+"=true"; //$NON-NLS-1$ //$NON-NLS-2$
if (expressionTracingThreshold != -1)
cvmArgs[cvmArgsCount++] = "-D"+ExpressionCommands.EXPRESSIONTRACE_TIMER_THRESHOLD+'='+String.valueOf(expressionTracingThreshold); //$NON-NLS-1$
}
// If in debug mode, we need to find a port for it to use.
int dport = -1;
if (debugMode) {
dport = findUnusedLocalPort("localhost", 5000, 15000, new int[0]); //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Djava.compiler=NONE"; //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Xdebug"; //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Xnoagent"; //$NON-NLS-1$
cvmArgs[cvmArgsCount++] = "-Xrunjdwp:transport=dt_socket,server=y,address=" + dport; //$NON-NLS-1$
}
if (!javaLibPaths.isEmpty()) {
StringBuffer appendTo = null;
if (existingLibpaths != -1) {
appendTo = new StringBuffer(evmArgs[existingLibpaths]);
appendTo.append(File.pathSeparatorChar); // Plus a separator so we can append
} else
appendTo = new StringBuffer("-Djava.library.path="); //$NON-NLS-1$
String [] libPaths = ProxyLaunchSupport.convertURLsToStrings((URL[]) javaLibPaths.toArray(new URL[javaLibPaths.size()]));
for (int i = 0; i < libPaths.length; i++) {
if (i != 0)
appendTo.append(File.pathSeparator);
appendTo.append(libPaths[i]);
}
if (existingLibpaths != -1)
cvmArgs[extraArgs+existingLibpaths] = appendTo.toString();
else
cvmArgs[extraArgs-1] = appendTo.toString();
}
runConfig.setProgramArguments(execArgs.getProgramArgumentsArray());
runConfig.setEnvironment(envp);
runConfig.setVMArguments(cvmArgs);
runConfig.setWorkingDirectory(workingDirName);
runConfig.setVMSpecificAttributesMap(vmAttributesMap);
// Bootpath
runConfig.setBootClassPath(getBootpath(configuration));
// check for cancellation
if (pm.isCanceled())
return;
pm.worked(100);
// set the default source locator if required
setDefaultSourceLocator(launch, configuration);
// Launch the configuration - 1 unit of work
runner.run(runConfig, launch, new SubProgressMonitor(pm, 100));
// check for cancellation
if (pm.isCanceled())
return;
IProcess[] processes = launch.getProcesses();
IProcess process = processes[0]; // There is only one.
// Check if it is already terminated. If it is, then there was a bad error, so just
// print out the results from it.
if (process.isTerminated()) {
IStreamsProxy stProxy = process.getStreamsProxy();
// Using a printWriter for println capability, but it needs to be on another
// writer, which will be string
java.io.StringWriter s = new java.io.StringWriter();
java.io.PrintWriter w = new java.io.PrintWriter(s);
w.println(ProxyRemoteMessages.VM_TERMINATED_INFO_);
w.println(ProxyRemoteMessages.VM_COMMAND_LINE);
w.println(process.getAttribute(IProcess.ATTR_CMDLINE));
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE1);
w.println(stProxy.getErrorStreamMonitor().getContents());
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE2);
w.println(stProxy.getOutputStreamMonitor().getContents());
w.println(ProxyRemoteMessages.VM_TERMINATED_LINE3);
w.close();
String msg = MessageFormat.format(ProxyRemoteMessages.Proxy_Terminated_too_soon_ERROR_, new Object[] { name });
dh.displayErrorMessage(ProxyRemoteMessages.Proxy_Error_Title, msg);
throw new CoreException(
new Status(IStatus.WARNING, ProxyPlugin.getPlugin().getBundle().getSymbolicName(), 0, s.toString(), null));
} else {
final String traceName = name;
IStreamsProxy fStreamsProxy = process.getStreamsProxy();
/**
* StreamListener. Should not be created if ProxyPlugin logger is not logging the requested level.
*
* @since 1.1.0
*/
class StreamListener implements IStreamListener {
String tracePrefix;
Level level;
Job printJob; // Job to try to gather printing together.
Logger logger;
StringBuffer gatheredText = new StringBuffer(100);
{
logger = ProxyPlugin.getPlugin().getLogger();
printJob = new Job("") { //$NON-NLS-1$
protected IStatus run(IProgressMonitor monitor) {
monitor.beginTask(ProxyRemoteMessages.LocalProxyLaunchDelegate_Monitor_PrintRemoteTrace_Text, 1);
while(true) {
String output = null;
synchronized (gatheredText) {
if (gatheredText.length() <= tracePrefix.length())
break; // We've reached the end, no more to print.
output = gatheredText.toString();
gatheredText.setLength(tracePrefix.length()); // Reset the length to the prefix.
}
logger.log(output, level);
}
monitor.done();
return Status.OK_STATUS;
}
};
printJob.setPriority(Job.SHORT);
printJob.setSystem(true);
}
public StreamListener(String type, Level level, Logger logger) {
tracePrefix = traceName + ':' + type + '>' + System.getProperty("line.separator"); //$NON-NLS-1$
gatheredText.append(tracePrefix);
this.level = level;
this.logger = logger;
}
public void streamAppended(String newText, IStreamMonitor monitor) {
synchronized(gatheredText) {
gatheredText.append(newText);
}
printJob.schedule(100L); // Wait tenth of second to gather as much as can together.
}
};
Logger logger = ProxyPlugin.getPlugin().getLogger();
if (logger.isLoggingLevel(Level.WARNING)) {
// Always listen to System.err output if we are at least logging warnings.
IStreamMonitor monitor = fStreamsProxy.getErrorStreamMonitor();
if (monitor != null)
monitor.addListener(new StreamListener("err", Level.WARNING, logger)); //$NON-NLS-1$
}
// If debug trace is requested, then attach trace listener for System.out
// Expression tracing requires debug trace too because it prints to sysout. However, it requesting expressionTracing, change logging level to INFO,
// we want them to show if this true. It is confusing to also have to change logging level in .options file.
if (useExpressionTracing)
if (!logger.isLoggingLevel(Level.INFO))
logger.setLevel(Level.INFO);
if (useExpressionTracing || "true".equalsIgnoreCase(Platform.getDebugOption(ProxyPlugin.getPlugin().getBundle().getSymbolicName() + ProxyRemoteUtil.DEBUG_VM_TRACEOUT))) { //$NON-NLS-1$
// Want to trace the output of the remote vm's. And we are logging at least level info.
if (logger.isLoggingLevel(Level.INFO)) {
IStreamMonitor monitor = fStreamsProxy.getOutputStreamMonitor();
if (monitor != null)
monitor.addListener(new StreamListener("out", Level.INFO, logger)); //$NON-NLS-1$
}
}
}
// If in debug mode, tester must start debugger before going on.
if (debugMode) {
if (!dh.promptPort(dport)) {
process.terminate();
throw new CoreException(
new Status(
IStatus.WARNING,
ProxyPlugin.getPlugin().getBundle().getSymbolicName(),
0,
"Debugger attach canceled", //$NON-NLS-1$
null));
}
}
// Now set up the registry.
registry.initializeRegistry(process);
new REMStandardBeanTypeProxyFactory(registry);
new REMStandardBeanProxyFactory(registry);
new REMMethodProxyFactory(registry);
if (debugMode || REMProxyFactoryRegistry.fGlobalNoTimeouts)
registry.fNoTimeouts = true;
if (configuration.getAttribute(IProxyConstants.ATTRIBUTE_AWT_SWING, true))
REMRegisterAWT.registerAWT(registry);
launchInfo.resultRegistry = registry;
pm.done();
TimerTests.basicTest.stopStep(stepId);
}
/**
* @param controller
* @param info
* @throws CoreException
*
* @since 1.0.2
*/
private void addInFragmentLibraries(IConfigurationContributionController controller, IConfigurationContributionInfo info) throws CoreException {
IPDEContributeClasspath instance = IPDEContributeClasspath.INSTANCE;
if (instance != null) {
instance.getPDEContributions(controller, info);
}
}
// Utilities to find the free port
private static final Random fgRandom = new Random(System.currentTimeMillis());
private static int findUnusedLocalPort(String host, int searchFrom, int searchTo, int[] exclude) {
for (int i = 0; i < 10; i++) {
int port = 0;
newport : while (true) {
port = getRandomPort(searchFrom, searchTo);
if (exclude != null)
for (int e = 0; e < exclude.length; e++)
if (port == exclude[e])
continue newport;
break;
}
try {
new Socket(host, port);
} catch (ConnectException e) {
return port;
} catch (IOException e) {
}
}
return -1;
}
private static int getRandomPort(int low, int high) {
return (int) (fgRandom.nextFloat() * (high - low)) + low;
}
private String[][] getBootpathExt(Map vmMap) {
String[][] ext = new String[3][];
if (vmMap != null) {
ext[0] = (String[]) vmMap.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_PREPEND);
ext[1] = (String[]) vmMap.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH);
ext[2] = (String[]) vmMap.get(IJavaLaunchConfigurationConstants.ATTR_BOOTPATH_APPEND);
}
return ext;
}
}