| /******************************************************************************* |
| * 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; |
| } |
| |
| |
| } |