| /******************************************************************************* |
| * Copyright (c) 2011, 2015 Wind River Systems, Inc. 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: |
| * Wind River Systems - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.tcf.te.tcf.processes.core.launcher; |
| |
| import java.io.BufferedWriter; |
| import java.io.FileWriter; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.InputStreamReader; |
| import java.io.OutputStream; |
| import java.io.OutputStreamWriter; |
| import java.io.PipedInputStream; |
| import java.io.PipedOutputStream; |
| import java.text.DateFormat; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.concurrent.atomic.AtomicReference; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Path; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.PlatformObject; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.osgi.util.NLS; |
| import org.eclipse.tcf.core.Command; |
| import org.eclipse.tcf.protocol.IChannel; |
| import org.eclipse.tcf.protocol.IChannel.IChannelListener; |
| import org.eclipse.tcf.protocol.IErrorReport; |
| import org.eclipse.tcf.protocol.IPeer; |
| import org.eclipse.tcf.protocol.IToken; |
| import org.eclipse.tcf.protocol.Protocol; |
| import org.eclipse.tcf.services.IProcesses; |
| import org.eclipse.tcf.services.IProcesses.ProcessContext; |
| import org.eclipse.tcf.services.IProcessesV1; |
| import org.eclipse.tcf.services.IStreams; |
| import org.eclipse.tcf.te.runtime.callback.AsyncCallbackCollector; |
| import org.eclipse.tcf.te.runtime.callback.Callback; |
| import org.eclipse.tcf.te.runtime.events.EventManager; |
| import org.eclipse.tcf.te.runtime.interfaces.callback.ICallback; |
| import org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer; |
| import org.eclipse.tcf.te.runtime.utils.StatusHelper; |
| import org.eclipse.tcf.te.tcf.core.Tcf; |
| import org.eclipse.tcf.te.tcf.core.async.CallbackInvocationDelegate; |
| import org.eclipse.tcf.te.tcf.core.channelmanager.OpenChannelException; |
| import org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager; |
| import org.eclipse.tcf.te.tcf.core.streams.StreamsDataProvider; |
| import org.eclipse.tcf.te.tcf.core.streams.StreamsDataReceiver; |
| import org.eclipse.tcf.te.tcf.processes.core.activator.CoreBundleActivator; |
| import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessContextAwareListener; |
| import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher; |
| import org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessStreamsProxy; |
| import org.eclipse.tcf.te.tcf.processes.core.interfaces.tracing.ITraceIds; |
| import org.eclipse.tcf.te.tcf.processes.core.nls.Messages; |
| import org.eclipse.tm.terminal.view.core.TerminalServiceFactory; |
| import org.eclipse.tm.terminal.view.core.interfaces.ITerminalService; |
| import org.eclipse.tm.terminal.view.core.interfaces.ITerminalTabListener; |
| import org.eclipse.tm.terminal.view.core.interfaces.constants.ITerminalsConnectorConstants; |
| |
| /** |
| * Remote process launcher. |
| * <p> |
| * The process launcher is implemented fully asynchronous. |
| */ |
| public class ProcessLauncher extends PlatformObject implements IProcessLauncher { |
| // The channel instance |
| /* default */ IChannel channel = null; |
| // Flag to signal if the channel needs to be closed on disposed |
| /* default */ boolean closeChannelOnDispose = false; |
| // Flag to signal if the channel is a private or shared channel |
| /* default */ boolean sharedChannel = false; |
| // The process properties instance |
| /* default */ IPropertiesContainer properties; |
| |
| // The processes service instance |
| /* default */ IProcesses svcProcesses; |
| // The streams service instance |
| /* default */ IStreams svcStreams; |
| // The remote process context |
| /* default */ IProcesses.ProcessContext processContext; |
| |
| // The callback instance |
| private ICallback callback; |
| |
| // The streams listener instance |
| private IChannelManager.IStreamsListener streamsListener = null; |
| // The process listener instance |
| private IProcesses.ProcessesListener processesListener = null; |
| // The terminal tab listener instance |
| private ITerminalTabListener terminalTabListener = null; |
| |
| // The streams proxy instance |
| private IProcessStreamsProxy streamsProxy = null; |
| |
| // The active token. |
| IToken activeToken = null; |
| |
| /** |
| * Message ID for error message in case the process launch failed. |
| */ |
| public static final String PROCESS_LAUNCH_FAILED_MESSAGE = "processLaunchFailedMessage"; //$NON-NLS-1$ |
| |
| /** |
| * Constructor. |
| */ |
| public ProcessLauncher() { |
| super(); |
| } |
| |
| /** |
| * Constructor. |
| */ |
| public ProcessLauncher(IProcessStreamsProxy streamsProxy) { |
| super(); |
| this.streamsProxy = streamsProxy; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#dispose() |
| */ |
| @Override |
| public void dispose() { |
| // Unlink the process context |
| processContext = null; |
| |
| // Store a final reference to the channel instance |
| final IChannel finChannel = channel; |
| |
| // Remove the notification listener |
| if (terminalTabListener != null) { |
| TerminalServiceFactory.getService().removeTerminalTabListener(terminalTabListener); |
| terminalTabListener = null; |
| } |
| |
| // Create the callback collector |
| final AsyncCallbackCollector collector = new AsyncCallbackCollector(new Callback() { |
| @Override |
| protected void internalDone(Object caller, IStatus status) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| // Close the channel as all disposal is done |
| if (finChannel != null && closeChannelOnDispose) { |
| Tcf.getChannelManager().closeChannel(finChannel); |
| } |
| } |
| }, new CallbackInvocationDelegate()); |
| |
| if (streamsListener != null) { |
| // Dispose the streams listener |
| if (streamsListener instanceof ProcessStreamsListener) { |
| ((ProcessStreamsListener)streamsListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector)); |
| } |
| streamsListener = null; |
| } |
| |
| // Dispose the processes listener if created |
| if (processesListener != null) { |
| // Remove the processes listener from the processes service |
| getSvcProcesses().removeListener(processesListener); |
| |
| // Dispose the processes listener |
| if (processesListener instanceof ProcessProcessesListener) { |
| ((ProcessProcessesListener)processesListener).dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector)); |
| } |
| |
| processesListener = null; |
| } |
| |
| // Dispose the streams proxy if created |
| if (streamsProxy != null) { |
| streamsProxy.dispose(new AsyncCallbackCollector.SimpleCollectorCallback(collector)); |
| streamsProxy = null; |
| } |
| // Mark the collector initialization as done |
| collector.initDone(); |
| |
| // Dissociate the channel |
| channel = null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#terminate() |
| */ |
| @Override |
| public void terminate() { |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| if (channel != null && channel.getState() == IChannel.STATE_OPEN) { |
| if (processContext != null && processContext.canTerminate()) { |
| // Try to terminate the process the usual way first (sending SIGTERM) |
| processContext.terminate(new IProcesses.DoneCommand() { |
| @Override |
| public void doneCommand(IToken token, Exception error) { |
| onTerminateDone(processContext, error); |
| } |
| }); |
| } |
| } |
| } |
| }; |
| |
| Protocol.invokeLater(runnable); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#cancel() |
| */ |
| @Override |
| public void cancel() { |
| if (activeToken != null && (callback == null || !callback.isDone())) { |
| final IToken token = activeToken; |
| activeToken = null; |
| Protocol.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| token.cancel(); |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Check if the process context really died after sending SIGTERM. |
| * <p> |
| * Called from {@link #terminate()}. |
| * |
| * @param context The process context. Must not be <code>null</code>. |
| * @param error The exception in case {@link #terminate()} returned with an error or <code>null</code>. |
| */ |
| protected void onTerminateDone(IProcesses.ProcessContext context, Exception error) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(context); |
| |
| // If the terminate of the remote process context failed, give a warning to the user |
| if (error != null) { |
| String message = NLS.bind(Messages.ProcessLauncher_error_processTerminateFailed, context.getName()); |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, StatusHelper.unwrapErrorReport(error.getLocalizedMessage())); |
| |
| IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status); |
| |
| // Dispose the launcher directly |
| dispose(); |
| } |
| // No error from terminate, this does not mean that the process went down |
| // really -> SIGTERM might have been ignored from the process! |
| else { |
| final IProcesses.ProcessContext finContext = context; |
| // Let's see if we can still get information about the context |
| getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, ProcessContext context) { |
| // In case there is no error and we do get back an process context, |
| // the process must be still running, having ignored the SIGTERM. |
| if (error == null && context != null && context.getID().equals(finContext.getID())) { |
| // Let's send a SIGHUP next. |
| getSvcProcesses().signal(context.getID(), 15, new IProcesses.DoneCommand() { |
| @Override |
| public void doneCommand(IToken token, Exception error) { |
| onSignalSIGHUPDone(finContext, error); |
| } |
| }); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Check if the process context died after sending SIGHUP. |
| * <p> |
| * Called from {@link #onTerminateDone(IProcesses.ProcessContext, Exception)}. |
| * |
| * @param context The process context. Must not be <code>null</code>. |
| * @param error The exception in case sending the signal returned with an error or <code>null</code>. |
| */ |
| protected void onSignalSIGHUPDone(IProcesses.ProcessContext context, Exception error) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| Assert.isNotNull(context); |
| |
| // If the terminate of the remote process context failed, give a warning to the user |
| if (error != null) { |
| String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGHUP(15)", context.getName()); //$NON-NLS-1$ |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, StatusHelper.unwrapErrorReport(error.getLocalizedMessage())); |
| |
| IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status); |
| |
| // Dispose the launcher directly |
| dispose(); |
| } |
| // No error from terminate, this does not mean that the process went down |
| // really -> SIGTERM might have been ignored from the process! |
| else { |
| final IProcesses.ProcessContext finContext = context; |
| // Let's see if we can still get information about the context |
| getSvcProcesses().getContext(context.getID(), new IProcesses.DoneGetContext() { |
| @Override |
| public void doneGetContext(IToken token, Exception error, ProcessContext context) { |
| // In case there is no error and we do get back an process context, |
| // the process must be still running, having ignored the SIGHUP. |
| if (error == null && context != null && context.getID().equals(finContext.getID())) { |
| // Finally send a SIGKILL. |
| getSvcProcesses().signal(context.getID(), 9, new IProcesses.DoneCommand() { |
| @Override |
| public void doneCommand(IToken token, Exception error) { |
| if (error != null) { |
| String message = NLS.bind(Messages.ProcessLauncher_error_processSendSignalFailed, "SIGKILL(15)", finContext.getName()); //$NON-NLS-1$ |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, StatusHelper.unwrapErrorReport(error.getLocalizedMessage())); |
| |
| IStatus status = new Status(IStatus.WARNING, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| Platform.getLog(CoreBundleActivator.getContext().getBundle()).log(status); |
| |
| // Dispose the launcher |
| dispose(); |
| } |
| } |
| }); |
| } |
| } |
| }); |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.processes.core.interfaces.launcher.IProcessLauncher#launch(org.eclipse.tcf.protocol.IPeer, org.eclipse.tcf.te.runtime.interfaces.properties.IPropertiesContainer, org.eclipse.tcf.te.runtime.interfaces.callback.ICallback) |
| */ |
| @Override |
| public void launch(final IPeer peer, final IPropertiesContainer properties, final ICallback callback) { |
| Assert.isNotNull(peer); |
| Assert.isNotNull(properties); |
| |
| // Normalize the callback |
| if (callback == null) { |
| this.callback = new Callback() { |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.runtime.callback.Callback#internalDone(java.lang.Object, org.eclipse.core.runtime.IStatus) |
| */ |
| @Override |
| public void internalDone(Object caller, IStatus status) { |
| } |
| }; |
| } |
| else { |
| this.callback = callback; |
| } |
| |
| // Remember the process properties |
| this.properties = properties; |
| |
| // Check if we get the channel to use passed in by the launch properties |
| if (properties.containsKey(IProcessLauncher.PROP_CHANNEL)) { |
| IChannel c = (IChannel) properties.getProperty(IProcessLauncher.PROP_CHANNEL); |
| if (c != null && c.getState() == IChannel.STATE_OPEN) { |
| channel = c; |
| closeChannelOnDispose = false; |
| sharedChannel = true; |
| } |
| } |
| |
| // Open a dedicated channel to the given peer |
| final Map<String, Boolean> flags = new HashMap<String, Boolean>(); |
| flags.put(IChannelManager.FLAG_FORCE_NEW, properties.containsKey(IChannelManager.FLAG_FORCE_NEW) ? Boolean.valueOf(properties.getBooleanProperty(IChannelManager.FLAG_FORCE_NEW)) : Boolean.TRUE); |
| if (channel != null && channel.getState() == IChannel.STATE_OPEN) { |
| Protocol.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| onChannelOpenDone(peer); |
| } |
| }); |
| } |
| else { |
| Tcf.getChannelManager().openChannel(peer, flags, new IChannelManager.DoneOpenChannel() { |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneOpenChannel#doneOpenChannel(java.lang.Throwable, org.eclipse.tcf.protocol.IChannel) |
| */ |
| @Override |
| public void doneOpenChannel(Throwable error, IChannel channel) { |
| if (error == null) { |
| ProcessLauncher.this.channel = channel; |
| ProcessLauncher.this.closeChannelOnDispose = true; |
| ProcessLauncher.this.sharedChannel = !flags.get(IChannelManager.FLAG_FORCE_NEW).booleanValue(); |
| |
| onChannelOpenDone(peer); |
| } else { |
| String detail = error.getLocalizedMessage(); |
| if (detail == null && error instanceof OpenChannelException) { |
| Throwable inner = ((OpenChannelException) error).getError(); |
| if (inner instanceof IErrorReport) |
| detail = Command.toErrorString(((IErrorReport)inner).getAttributes()); |
| else |
| detail = inner.getLocalizedMessage(); |
| } |
| if (detail == null) |
| detail = "N/A"; //$NON-NLS-1$ |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getName(), detail), |
| error); |
| invokeCallback(status, null); |
| } |
| } |
| }); |
| } |
| } |
| |
| protected void onChannelOpenDone(final IPeer peer) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Attach a channel listener so we can dispose ourself if the channel |
| // is closed from the remote side. |
| channel.addChannelListener(new IChannelListener() { |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelOpened() |
| */ |
| @Override |
| public void onChannelOpened() { |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#onChannelClosed(java.lang.Throwable) |
| */ |
| @Override |
| public void onChannelClosed(Throwable error) { |
| if (error != null) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_channelConnectFailed, peer.getID(), error.getLocalizedMessage()), |
| error); |
| invokeCallback(status, null); |
| } |
| } |
| /* (non-Javadoc) |
| * @see org.eclipse.tcf.protocol.IChannel.IChannelListener#congestionLevel(int) |
| */ |
| @Override |
| public void congestionLevel(int level) { |
| } |
| }); |
| |
| // Check if the channel is in connected state |
| if (channel.getState() != IChannel.STATE_OPEN) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| Messages.ProcessLauncher_error_channelNotConnected, |
| new IllegalStateException()); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // Do some very basic sanity checking on the process properties |
| if (properties.getStringProperty(PROP_PROCESS_PATH) == null) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| Messages.ProcessLauncher_error_missingProcessPath, |
| new IllegalArgumentException(PROP_PROCESS_PATH)); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // Check if the user wants to force the use of the IProcesses service |
| String property = System.getProperty("processLauncher.force.IProcesses"); //$NON-NLS-1$ |
| boolean forceIProcesses = property != null ? Boolean.parseBoolean(property) : false; |
| |
| // Get the process and streams services. Try the V1 processes service first |
| // before falling back to the standard processes service. |
| if (!forceIProcesses) svcProcesses = channel.getRemoteService(IProcessesV1.class); |
| if (svcProcesses == null) svcProcesses = channel.getRemoteService(IProcesses.class); |
| if (svcProcesses == null) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IProcesses.class.getName()), |
| null); |
| |
| invokeCallback(status, null); |
| return; |
| } |
| |
| svcStreams = channel.getRemoteService(IStreams.class); |
| if (svcStreams == null) { |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_missingRequiredService, IStreams.class.getName()), |
| null); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // Execute the launch |
| executeLaunch(); |
| } |
| |
| /** |
| * Executes the launch of the remote process. |
| */ |
| protected void executeLaunch() { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Get the process properties container |
| final IPropertiesContainer properties = getProperties(); |
| if (properties == null) { |
| // This is an illegal argument. Properties must be set |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$ |
| new IllegalArgumentException()); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // If a console should be associated, a streams listener needs to be created |
| if (streamsProxy != null |
| || properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE) |
| || properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) { |
| // Create the streams listener |
| streamsListener = createStreamsListener(); |
| // If available, we need to subscribe to the streams. |
| if (streamsListener != null) { |
| // Subscribe the streams service |
| Tcf.getChannelManager().subscribeStream(channel, getSvcProcesses() instanceof IProcessesV1 ? IProcessesV1.NAME : IProcesses.NAME, streamsListener, new IChannelManager.DoneSubscribeStream() { |
| |
| @Override |
| public void doneSubscribeStream(Throwable error) { |
| // In case the subscribe to the stream fails, we pass on |
| // the error to the user and stop the launch |
| if (error != null) { |
| // Construct the error message to show to the user |
| String message = NLS.bind(getProcessLaunchFailedMessageTemplate(), |
| properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH), |
| makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS))); |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, Messages.ProcessLauncher_cause_subscribeFailed); |
| |
| // Construct the status object |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| invokeCallback(status, null); |
| } else { |
| // Initialize the console or output file |
| onSubscribeStreamsDone(); |
| } |
| } |
| }); |
| } else { |
| // No streams to attach to -> go directly to the process launch |
| onAttachStreamsDone(); |
| } |
| } else { |
| // No streams to attach to -> go directly to the process launch |
| onAttachStreamsDone(); |
| } |
| } |
| |
| /** |
| * Initialize and attach the output console and/or the output file. |
| * <p> |
| * Called from {@link IChannelManager#subscribeStream(IChannel, String, org.eclipse.tcf.services.IStreams.StreamsListener, org.eclipse.tcf.te.tcf.core.interfaces.IChannelManager.DoneSubscribeStream)} |
| */ |
| protected void onSubscribeStreamsDone() { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Get the process properties container |
| IPropertiesContainer properties = getProperties(); |
| if (properties == null) { |
| // This is an illegal argument. Properties must be set |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$ |
| new IllegalArgumentException()); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // The streams got subscribed, check what we need to do with them |
| if (streamsProxy != null) { |
| // Publish the streams to the supplied proxy |
| streamsProxy.connectInputStreamMonitor(connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID })); |
| // Create and store the streams the terminal will see as stdout |
| streamsProxy.connectOutputStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }, null)); |
| // Create and store the streams the terminal will see as stderr |
| streamsProxy.connectErrorStreamMonitor(connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }, null)); |
| onAttachStreamsDone(); |
| } else if (properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE)) { |
| // We don't have a streams proxy, we default the output redirection to the standard terminals console view |
| |
| // Get the terminal service |
| ITerminalService terminal = TerminalServiceFactory.getService(); |
| // If not available, we cannot fulfill this request |
| if (terminal != null) { |
| // Register the terminal tab listener to listen to the terminal events |
| terminalTabListener = new ProcessLauncherTerminalTabListener(this); |
| terminal.addTerminalTabListener(terminalTabListener); |
| // Create the terminal streams settings |
| Map<String, Object> props = new HashMap<String, Object>(); |
| props.put(ITerminalsConnectorConstants.PROP_DELEGATE_ID, "org.eclipse.tm.terminal.connector.streams.launcher.streams"); //$NON-NLS-1$ |
| props.put(ITerminalsConnectorConstants.PROP_ID, "org.eclipse.tm.terminal.view.ui.TerminalsView"); //$NON-NLS-1$ |
| // Set the terminal tab title |
| String terminalTitle = properties.getStringProperty(ITerminalsConnectorConstants.PROP_TITLE); |
| if (terminalTitle == null) terminalTitle = getTerminalTitle(); |
| if (terminalTitle != null) props.put(ITerminalsConnectorConstants.PROP_TITLE, terminalTitle); |
| |
| // Get the process output listener list from the properties |
| Object value = properties.getProperty(PROP_PROCESS_OUTPUT_LISTENER); |
| StreamsDataReceiver.Listener[] listeners = value instanceof StreamsDataReceiver.Listener[] ? (StreamsDataReceiver.Listener[]) value : null; |
| |
| // Create and store the streams which will be connected to the terminals stdin |
| props.put(ITerminalsConnectorConstants.PROP_STREAMS_STDIN, connectRemoteOutputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDIN_ID })); |
| // Create and store the streams the terminal will see as stdout |
| props.put(ITerminalsConnectorConstants.PROP_STREAMS_STDOUT, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDOUT_ID }, listeners)); |
| // Create and store the streams the terminal will see as stderr |
| props.put(ITerminalsConnectorConstants.PROP_STREAMS_STDERR, connectRemoteInputStream(getStreamsListener(), new String[] { IProcesses.PROP_STDERR_ID }, null)); |
| |
| // Copy the terminal properties |
| props.put(ITerminalsConnectorConstants.PROP_LOCAL_ECHO, Boolean.valueOf(properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_LOCAL_ECHO))); |
| props.put(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR, properties.getStringProperty(ITerminalsConnectorConstants.PROP_LINE_SEPARATOR)); |
| props.put(ITerminalsConnectorConstants.PROP_FORCE_NEW, Boolean.valueOf(properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_FORCE_NEW))); |
| |
| // By default, a launched process cannot be reconnected once terminated. |
| boolean noReconnect = true; |
| if (properties.containsKey(ITerminalsConnectorConstants.PROP_DATA_NO_RECONNECT)) { |
| noReconnect = properties.getBooleanProperty(ITerminalsConnectorConstants.PROP_DATA_NO_RECONNECT); |
| } |
| props.put(ITerminalsConnectorConstants.PROP_DATA_NO_RECONNECT, Boolean.valueOf(noReconnect)); |
| |
| // The custom data object is the process launcher itself |
| props.put(ITerminalsConnectorConstants.PROP_DATA, this); |
| |
| // Initialize the process specific terminal state text representations |
| props.put("TabFolderManager_state_connected", Messages.ProcessLauncher_state_connected); //$NON-NLS-1$ |
| props.put("TabFolderManager_state_connecting", Messages.ProcessLauncher_state_connecting); //$NON-NLS-1$ |
| props.put("TabFolderManager_state_closed", Messages.ProcessLauncher_state_closed); //$NON-NLS-1$ |
| |
| // Open the console |
| terminal.openConsole(props, new ITerminalService.Done() { |
| @Override |
| public void done(IStatus status) { |
| if (status.getSeverity() == IStatus.ERROR) { |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // Launch the process (from within the TCF event dispatch thread) |
| Protocol.invokeLater(new Runnable() { |
| @Override |
| public void run() { |
| onAttachStreamsDone(); |
| } |
| }); |
| } |
| }); |
| } else { |
| onAttachStreamsDone(); |
| } |
| } else if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE) != null) { |
| // Get the file name where to redirect the process output to |
| String filename = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_OUTPUT_REDIRECT_TO_FILE); |
| try { |
| // Create the receiver instance. If the file already exist, we |
| // overwrite the file content. |
| StreamsDataReceiver receiver = new StreamsDataReceiver(new BufferedWriter(new FileWriter(filename)), |
| new String[] { IProcesses.PROP_STDOUT_ID, IProcesses.PROP_STDERR_ID }); |
| // Register the receiver to the streams listener |
| if (getStreamsListener() instanceof ProcessStreamsListener) { |
| ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver); |
| } |
| onAttachStreamsDone(); |
| } catch (IOException e) { |
| // Construct the error message to show to the user |
| String message = NLS.bind(getProcessLaunchFailedMessageTemplate(), |
| properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH), |
| makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS))); |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, |
| e.getLocalizedMessage() != null ? StatusHelper.unwrapErrorReport(e.getLocalizedMessage()) : Messages.ProcessLauncher_cause_ioexception); |
| |
| // Construct the status object |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, e); |
| invokeCallback(status, null); |
| return; |
| } |
| } |
| } |
| |
| /** |
| * Returns the message template for the process launch failed error message. |
| * |
| * @return The message template. |
| */ |
| protected String getProcessLaunchFailedMessageTemplate() { |
| if (properties != null && properties.containsKey(PROCESS_LAUNCH_FAILED_MESSAGE)) { |
| return properties.getStringProperty(PROCESS_LAUNCH_FAILED_MESSAGE); |
| } |
| return Messages.ProcessLauncher_error_processLaunchFailed; |
| } |
| |
| /** |
| * Returns the terminal title string. |
| * <p> |
| * The default implementation constructs a title like "<process> (Start time) [channel name]". |
| * |
| * @return The terminal title string or <code>null</code>. |
| */ |
| protected String getTerminalTitle() { |
| if (properties == null) { |
| return null; |
| } |
| |
| StringBuilder title = new StringBuilder(); |
| |
| IPath processPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH)); |
| IPath monitoredProcessPath = null; |
| if (properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH) != null) { |
| monitoredProcessPath = new Path(properties.getStringProperty(IProcessLauncher.PROP_PROCESS_MONITORED_PATH)); |
| } |
| |
| // In case, we do have a monitored process path here, we construct the title |
| // as <monitor_app_basename>: <monitored_app>" |
| if (monitoredProcessPath != null) { |
| title.append(processPath.lastSegment()); |
| title.append(": "); //$NON-NLS-1$ |
| processPath = monitoredProcessPath; |
| } |
| |
| // Avoid very long terminal title's by shortening the path if it has more than 3 segments |
| if (processPath.segmentCount() > 3) { |
| title.append(".../"); //$NON-NLS-1$ |
| title.append(processPath.lastSegment()); |
| } else { |
| title.append(processPath.toString()); |
| } |
| |
| // Get the peer name |
| final AtomicReference<String> peerName = new AtomicReference<String>(getProperties().getStringProperty(IProcessLauncher.PROP_CONNECTION_NAME)); |
| if (peerName.get() == null) { |
| // Query the peer from the open channel |
| Runnable runnable = new Runnable() { |
| @Override |
| public void run() { |
| if (channel != null) { |
| peerName.set(channel.getRemotePeer().getName()); |
| } |
| } |
| }; |
| |
| if (Protocol.isDispatchThread()) runnable.run(); |
| else Protocol.invokeAndWait(runnable); |
| } |
| |
| if (peerName.get() != null) { |
| title.append(" [").append(peerName.get()).append("]"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| DateFormat format = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT); |
| String date = format.format(new Date(System.currentTimeMillis())); |
| title.append(" (").append(date).append(")"); //$NON-NLS-1$ //$NON-NLS-2$ |
| |
| return title.toString(); |
| } |
| |
| /** |
| * Connects the given stream id's to a local {@link InputStream} instance. |
| * |
| * @param streamsListener The streams listener. Must not be <code>null</code>. |
| * @param streamIds The stream id's. Must not be <code>null</code>. |
| * @param listeners A set of listeners to register to the streams data receiver or <code>null</code>. |
| * |
| * @return The local input stream instance or <code>null</code>. |
| */ |
| protected InputStream connectRemoteInputStream(IStreams.StreamsListener streamsListener, String[] streamIds, StreamsDataReceiver.Listener[] listeners) { |
| Assert.isNotNull(streamsListener); |
| Assert.isNotNull(streamIds); |
| |
| InputStream stream = null; |
| |
| // Create the output stream receiving the data from remote |
| PipedOutputStream remoteStreamDataReceiverStream = new PipedOutputStream(); |
| // Create the piped input stream instance |
| try { stream = new PipedInputStream(remoteStreamDataReceiverStream); } catch (IOException e) { /* ignored on purpose */ } |
| |
| // If the input stream creation succeeded, connect the data receiver |
| if (stream != null) { |
| StreamsDataReceiver receiver = new StreamsDataReceiver(new OutputStreamWriter(remoteStreamDataReceiverStream), streamIds); |
| |
| // Register the listeners if given |
| if (listeners != null && listeners.length > 0) { |
| for (StreamsDataReceiver.Listener listener : listeners) { |
| receiver.addListener(listener); |
| } |
| } |
| |
| // Register the data receiver to the streams listener |
| if (getStreamsListener() instanceof ProcessStreamsListener) { |
| ((ProcessStreamsListener)getStreamsListener()).registerDataReceiver(receiver); |
| } |
| } |
| |
| return stream; |
| } |
| |
| /** |
| * Connects the given stream id's to a local {@link OutputStream} instance. |
| * |
| * @param streamsListener The streams listener. Must not be <code>null</code>. |
| * @param streamIds The stream id's. Must not be <code>null</code>. |
| * |
| * @return The local output stream instance or <code>null</code>. |
| */ |
| protected OutputStream connectRemoteOutputStream(IStreams.StreamsListener streamsListener, String[] streamIds) { |
| Assert.isNotNull(streamsListener); |
| Assert.isNotNull(streamIds); |
| |
| PipedInputStream inStream = null; |
| |
| // Create the output stream receiving the data from local |
| PipedOutputStream stream = new PipedOutputStream(); |
| // Create the piped input stream instance |
| try { inStream = new PipedInputStream(stream); } catch (IOException e) { stream = null; } |
| |
| // If the stream creation succeeded, connect the data provider |
| if (stream != null && inStream != null) { |
| StreamsDataProvider provider = new StreamsDataProvider(new InputStreamReader(inStream), streamIds); |
| // Register the data provider to the streams listener |
| if (getStreamsListener() instanceof ProcessStreamsListener) { |
| ((ProcessStreamsListener)getStreamsListener()).setDataProvider(provider); |
| } |
| } |
| |
| return stream; |
| } |
| |
| /** |
| * Queries the initial new process environment from remote. |
| */ |
| protected void onAttachStreamsDone() { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Query the default environment for a new process |
| getSvcProcesses().getEnvironment(new IProcesses.DoneGetEnvironment() { |
| @Override |
| public void doneGetEnvironment(IToken token, Exception error, Map<String, String> environment) { |
| if (error != null) { |
| // Construct the error message to show to the user |
| String message = Messages.ProcessLauncher_error_getEnvironmentFailed; |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, |
| error.getLocalizedMessage() != null ? StatusHelper.unwrapErrorReport(error.getLocalizedMessage()) : Messages.ProcessLauncher_cause_startFailed); |
| |
| // Construct the status object |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| invokeCallback(status, null); |
| } else { |
| // Initiate the process launch |
| onGetEnvironmentDone(environment); |
| } |
| } |
| }); |
| } |
| |
| /** |
| * Initiate the process launch. |
| * <p> |
| * Called from {@link #executeLaunch()} or {@link #onAttachStreamsDone()}. |
| */ |
| protected void onGetEnvironmentDone(final Map<String, String> environment) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Get the process properties container |
| final IPropertiesContainer properties = getProperties(); |
| if (properties == null) { |
| // This is an illegal argument. Properties must be set |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), |
| NLS.bind(Messages.ProcessLauncher_error_illegalNullArgument, "properties"), //$NON-NLS-1$ |
| new IllegalArgumentException()); |
| invokeCallback(status, null); |
| return; |
| } |
| |
| // Create the process listener |
| processesListener = createProcessesListener(); |
| if (processesListener != null) { |
| getSvcProcesses().addListener(processesListener); |
| } |
| |
| // Get the process attributes |
| String processPath = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH); |
| |
| Boolean processArgsAsIs = (Boolean)properties.getProperty(IProcessLauncher.PROP_USE_PROCESS_ARGS_AS_IS); |
| String[] processArgs = (String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS); |
| // Assure that the first argument is the process path itself |
| if (!(processArgs != null && processArgs.length > 0 && processPath.equals(processArgs[0]))) { |
| // Prepend the process path to the list of arguments |
| List<String> args = processArgs != null ? new ArrayList<String>(Arrays.asList(processArgs)) : new ArrayList<String>(); |
| if (processArgsAsIs == null || !processArgsAsIs.booleanValue()) { |
| args.add(0, processPath); |
| } |
| processArgs = args.toArray(new String[args.size()]); |
| } |
| |
| Boolean processCWDAsIs = (Boolean)properties.getProperty(IProcessLauncher.PROP_USE_PROCESS_CWD_AS_IS); |
| String processCWD = properties.getStringProperty(IProcessLauncher.PROP_PROCESS_CWD); |
| // If the process working directory is not explicitly set, default to the process path directory |
| if (processCWD == null || "".equals(processCWD.trim())) { //$NON-NLS-1$ |
| processCWD = (processCWDAsIs == null || !processCWDAsIs.booleanValue()) ? new Path(processPath).removeLastSegments(1).toString() : ""; //$NON-NLS-1$ |
| } |
| |
| // Merge the initial process environment and the desired process environment |
| Map<String, String> processEnv = new HashMap<String, String>(environment); |
| @SuppressWarnings("unchecked") |
| Map<String, String> processEnvDiff = (Map<String, String>)properties.getProperty(IProcessLauncher.PROP_PROCESS_ENV); |
| mergeEnvironment(processEnv, processEnvDiff); |
| |
| boolean processConsole = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ASSOCIATE_CONSOLE); |
| |
| if (processConsole) { |
| // Assure that the TERM variable is set to "xterm" |
| processEnv.put("TERM", "xterm"); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| boolean attach = properties.getBooleanProperty(IProcessLauncher.PROP_PROCESS_ATTACH); |
| |
| // Launch the remote process |
| if (getSvcProcesses() instanceof IProcessesV1) { |
| // Fill in the process launch parameter |
| Map<String, Object> params = new HashMap<String, Object>(); |
| |
| // Explicitly set the assumed defaults to achieve predictable behavior |
| // independent of the actual agent side implementation. |
| params.put(IProcessesV1.START_ATTACH, Boolean.FALSE); |
| params.put(IProcessesV1.START_ATTACH_CHILDREN, Boolean.FALSE); |
| params.put(IProcessesV1.START_STOP_AT_ENTRY, Boolean.FALSE); |
| params.put(IProcessesV1.START_STOP_AT_MAIN, Boolean.FALSE); |
| params.put(IProcessesV1.START_USE_TERMINAL, Boolean.FALSE); |
| |
| if (properties.getProperty(IProcessLauncher.PROP_PROCESSESV1_PARAMS) != null) { |
| @SuppressWarnings("unchecked") |
| Map<String, Object> addParams = (Map<String, Object>)properties.getProperty(IProcessLauncher.PROP_PROCESSESV1_PARAMS); |
| for (Entry<String,Object> entry : addParams.entrySet()) { |
| params.put(entry.getKey(), entry.getValue()); |
| } |
| } |
| else { |
| params.put(IProcessesV1.START_ATTACH, Boolean.valueOf(attach)); |
| params.put(IProcessesV1.START_ATTACH_CHILDREN, Boolean.valueOf(properties.getBooleanProperty(IProcessesV1.START_ATTACH_CHILDREN))); |
| params.put(IProcessesV1.START_STOP_AT_ENTRY, Boolean.valueOf(properties.getBooleanProperty(IProcessesV1.START_STOP_AT_ENTRY))); |
| params.put(IProcessesV1.START_STOP_AT_MAIN, Boolean.valueOf(properties.getBooleanProperty(IProcessesV1.START_STOP_AT_MAIN))); |
| params.put(IProcessesV1.START_USE_TERMINAL, Boolean.valueOf(processConsole)); |
| } |
| |
| activeToken = ((IProcessesV1)getSvcProcesses()).start(processCWD, processPath, processArgs, processEnv, params, new IProcesses.DoneStart() { |
| @Override |
| public void doneStart(IToken token, Exception error, ProcessContext process) { |
| activeToken = null; |
| if (error != null) { |
| // Construct the error message to show to the user |
| String message = NLS.bind(getProcessLaunchFailedMessageTemplate(), |
| properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH), |
| makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS))); |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, |
| error.getLocalizedMessage() != null ? StatusHelper.unwrapErrorReport(error.getLocalizedMessage()) : Messages.ProcessLauncher_cause_startFailed); |
| |
| // Construct the status object |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| invokeCallback(status, null); |
| } else { |
| // Register the process context to the listener |
| onProcessLaunchDone(process); |
| } |
| } |
| }); |
| } else { |
| activeToken = getSvcProcesses().start(processCWD, processPath, processArgs, processEnv, attach, new IProcesses.DoneStart() { |
| @Override |
| public void doneStart(IToken token, Exception error, ProcessContext process) { |
| activeToken = null; |
| if (error != null) { |
| // Construct the error message to show to the user |
| String message = NLS.bind(getProcessLaunchFailedMessageTemplate(), |
| properties.getStringProperty(IProcessLauncher.PROP_PROCESS_PATH), |
| makeString((String[])properties.getProperty(IProcessLauncher.PROP_PROCESS_ARGS))); |
| message += NLS.bind(Messages.ProcessLauncher_error_possibleCause, |
| error.getLocalizedMessage() != null ? StatusHelper.unwrapErrorReport(error.getLocalizedMessage()) : Messages.ProcessLauncher_cause_startFailed); |
| |
| // Construct the status object |
| IStatus status = new Status(IStatus.ERROR, CoreBundleActivator.getUniqueIdentifier(), message, error); |
| invokeCallback(status, null); |
| } else { |
| // Register the process context to the listener |
| onProcessLaunchDone(process); |
| } |
| } |
| }); |
| } |
| } |
| |
| /** |
| * Merge original channel environment and process environment. |
| * @param processEnv |
| * @param processEnvDiff |
| */ |
| protected void mergeEnvironment(Map<String,String> processEnv, Map<String,String> processEnvDiff) { |
| if (processEnv != null && processEnvDiff != null && !processEnvDiff.isEmpty()) { |
| processEnv.putAll(processEnvDiff); |
| } |
| } |
| |
| /** |
| * Register the process context to the listeners. |
| * |
| * @param process The process context or <code>null</code>. |
| */ |
| protected void onProcessLaunchDone(ProcessContext process) { |
| Assert.isTrue(Protocol.isDispatchThread(), "Illegal Thread Access"); //$NON-NLS-1$ |
| |
| // Register the process context with the listeners |
| if (process != null) { |
| if (CoreBundleActivator.getTraceHandler().isSlotEnabled(0, ITraceIds.TRACE_PROCESS_LAUNCHER)) { |
| CoreBundleActivator.getTraceHandler().trace("Process context created: id='" + process.getID() + "', name='" + process.getName() + "'", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ |
| 0, ITraceIds.TRACE_PROCESS_LAUNCHER, IStatus.INFO, getClass()); |
| } |
| |
| // Remember the process context |
| processContext = process; |
| |
| // Push the process context to the listeners |
| if (getStreamsListener() instanceof IProcessContextAwareListener) { |
| ((IProcessContextAwareListener)getStreamsListener()).setProcessContext(process); |
| } |
| if (getProcessesListener() instanceof IProcessContextAwareListener) { |
| ((IProcessContextAwareListener)getProcessesListener()).setProcessContext(process); |
| } |
| |
| // Send a notification |
| ProcessStateChangeEvent event = createRemoteProcessStateChangeEvent(process); |
| if (event != null) { |
| EventManager.getInstance().fireEvent(event); |
| } |
| } |
| |
| // Invoke the callback to signal that we are done |
| invokeCallback(Status.OK_STATUS, process); |
| } |
| |
| /** |
| * Creates a new remote process state change event instance. |
| * |
| * @param context The process context. Must not be <code>null</code>. |
| * @return The event instance or <code>null</code>. |
| */ |
| protected ProcessStateChangeEvent createRemoteProcessStateChangeEvent(IProcesses.ProcessContext context) { |
| Assert.isNotNull(context); |
| return new ProcessStateChangeEvent(context, ProcessStateChangeEvent.EVENT_PROCESS_CREATED, Boolean.FALSE, Boolean.TRUE, -1); |
| } |
| |
| /** |
| * Invoke the callback with the given parameters. If the given status severity |
| * is {@link IStatus#ERROR}, the process launcher object is disposed automatically. |
| * |
| * @param status The status. Must not be <code>null</code>. |
| * @param result The result object or <code>null</code>. |
| */ |
| protected void invokeCallback(IStatus status, Object result) { |
| // Dispose the process launcher if we report an error |
| if (status.getSeverity() == IStatus.ERROR) { |
| dispose(); |
| } |
| |
| // Invoke the callback |
| ICallback callback = getCallback(); |
| if (callback != null) { |
| callback.setResult(result); |
| callback.done(this, status); |
| } |
| } |
| |
| /** |
| * Returns the channel instance. |
| * |
| * @return The channel instance or <code>null</code> if none. |
| */ |
| public final IChannel getChannel() { |
| return channel; |
| } |
| |
| /** |
| * Returns if the channel is a private or shared channel. |
| * |
| * @return <code>True</code> if the channel a shared channel, <code>false</code> otherwise. |
| */ |
| public final boolean isSharedChannel() { |
| return sharedChannel; |
| } |
| |
| /** |
| * Returns the process properties container. |
| * |
| * @return The process properties container or <code>null</code> if none. |
| */ |
| public final IPropertiesContainer getProperties() { |
| return properties; |
| } |
| |
| /** |
| * Returns the processes service instance. |
| * |
| * @return The processes service instance or <code>null</code> if none. |
| */ |
| public final IProcesses getSvcProcesses() { |
| return svcProcesses; |
| } |
| |
| /** |
| * Returns the streams service instance. |
| * |
| * @return The streams service instance or <code>null</code> if none. |
| */ |
| public final IStreams getSvcStreams() { |
| return svcStreams; |
| } |
| |
| /** |
| * Returns the callback instance. |
| * |
| * @return The callback instance or <code>null</code> if none. |
| */ |
| protected final ICallback getCallback() { |
| return callback; |
| } |
| |
| /** |
| * Create the streams listener instance. |
| * |
| * @return The streams listener instance or <code>null</code> if none. |
| */ |
| protected IChannelManager.IStreamsListener createStreamsListener() { |
| return new ProcessStreamsListener(this); |
| } |
| |
| /** |
| * Returns the streams listener instance. |
| * |
| * @return The streams listener instance or <code>null</code>. |
| */ |
| protected final IStreams.StreamsListener getStreamsListener() { |
| return streamsListener; |
| } |
| |
| /** |
| * Create the processes listener instance. |
| * |
| * @return The processes listener instance or <code>null</code> if none. |
| */ |
| protected IProcesses.ProcessesListener createProcessesListener() { |
| return new ProcessProcessesListener(this); |
| } |
| |
| /** |
| * Returns the processes listener instance. |
| * |
| * @return The processes listener instance or <code>null</code> if none. |
| */ |
| protected final IProcesses.ProcessesListener getProcessesListener() { |
| return processesListener; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.eclipse.core.runtime.PlatformObject#getAdapter(java.lang.Class) |
| */ |
| @Override |
| public Object getAdapter(Class adapter) { |
| if (adapter.isAssignableFrom(IProcesses.ProcessesListener.class)) { |
| return processesListener; |
| } |
| else if (adapter.isAssignableFrom(IStreams.StreamsListener.class)) { |
| return streamsListener; |
| } |
| else if (adapter.isAssignableFrom(IStreams.class)) { |
| return svcStreams; |
| } |
| else if (adapter.isAssignableFrom(IProcesses.class)) { |
| return svcProcesses; |
| } |
| else if (adapter.isAssignableFrom(IChannel.class)) { |
| return channel; |
| } |
| else if (adapter.isAssignableFrom(IPropertiesContainer.class)) { |
| return properties; |
| } |
| else if (adapter.isAssignableFrom(IProcesses.ProcessContext.class)) { |
| return processContext; |
| } |
| else if (adapter.isAssignableFrom(this.getClass())) { |
| return this; |
| } |
| |
| |
| return super.getAdapter(adapter); |
| } |
| |
| /** |
| * Makes a space separated string from the given array. |
| * |
| * @param array The string array or <code>null</code>. |
| * @return The string. |
| */ |
| String makeString(String[] array) { |
| if (array == null) { |
| return ""; //$NON-NLS-1$ |
| } |
| StringBuilder result = new StringBuilder(); |
| for (String element : array) { |
| if (result.length() > 0) { |
| result.append(' '); |
| } |
| result.append(element); |
| } |
| return result.toString(); |
| } |
| } |