Fix NullPointerException in TimeChartView
[deliverable/tracecompass.git] / org.eclipse.linuxtools.lttng2.ui / src / org / eclipse / linuxtools / internal / lttng2 / ui / views / control / remote / CommandShell.java
CommitLineData
eb1bab5b
BH
1/**********************************************************************
2 * Copyright (c) 2012 Ericsson
ea21cd65 3 *
eb1bab5b
BH
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
ea21cd65
AM
8 *
9 * Contributors:
eb1bab5b
BH
10 * Patrick Tasse - Initial API and implementation
11 * Bernd Hufmann - Updated using Executor Framework
12 **********************************************************************/
9315aeee 13package org.eclipse.linuxtools.internal.lttng2.ui.views.control.remote;
eb1bab5b
BH
14
15import java.io.BufferedReader;
16import java.io.IOException;
17import java.io.InputStreamReader;
18import java.util.ArrayList;
d6fc6e1b 19import java.util.Random;
eb1bab5b
BH
20import java.util.concurrent.Callable;
21import java.util.concurrent.CancellationException;
22import java.util.concurrent.ExecutorService;
23import java.util.concurrent.Executors;
24import java.util.concurrent.FutureTask;
25import java.util.concurrent.TimeUnit;
26import java.util.concurrent.TimeoutException;
27
28import org.eclipse.core.commands.ExecutionException;
29import org.eclipse.core.runtime.IProgressMonitor;
30import org.eclipse.core.runtime.NullProgressMonitor;
9315aeee 31import org.eclipse.linuxtools.internal.lttng2.ui.views.control.messages.Messages;
eb1bab5b
BH
32import org.eclipse.rse.services.shells.HostShellProcessAdapter;
33import org.eclipse.rse.services.shells.IHostShell;
34import org.eclipse.rse.services.shells.IShellService;
35
36/**
eb1bab5b 37 * <p>
ea21cd65 38 * Implementation of remote command execution using RSE's shell service.
eb1bab5b 39 * </p>
cfdb727a 40 *
dbd4432d
BH
41 * @author Patrick Tasse
42 * @author Bernd Hufmann
eb1bab5b
BH
43 */
44public class CommandShell implements ICommandShell {
45
46 // ------------------------------------------------------------------------
47 // Constants
48 // ------------------------------------------------------------------------
49
d6fc6e1b 50 /** Sub-string to be echo'ed when running command in shell, used to indicate that the command has finished running */
eb1bab5b 51 public final static String DONE_MARKUP_STRING = "--RSE:donedonedone:--"; //$NON-NLS-1$
ea21cd65
AM
52
53 /** Sub-string to be echoed when running a command in shell. */
d6fc6e1b 54 public final static String BEGIN_END_TAG = "BEGIN-END-TAG:"; //$NON-NLS-1$
ea21cd65 55
cfdb727a 56 /** Command delimiter for shell */
eb1bab5b
BH
57 public final static String CMD_DELIMITER = "\n"; //$NON-NLS-1$
58
cfdb727a 59 /** Shell "echo" command */
eb1bab5b 60 public final static String SHELL_ECHO_CMD = " echo "; //$NON-NLS-1$
ea21cd65 61
d6fc6e1b
BH
62 /** Default command separator */
63 public final static char CMD_SEPARATOR = ';';
eb1bab5b 64
dadd7daa
BH
65 /** Default timeout value used for executing commands, in milliseconds */
66 private final static int DEFAULT_TIMEOUT_VALUE = 15000;
eb1bab5b
BH
67
68 // ------------------------------------------------------------------------
69 // Attributes
70 // ------------------------------------------------------------------------
71 private IRemoteSystemProxy fProxy = null;
72 private IHostShell fHostShell = null;
d6fc6e1b
BH
73 private BufferedReader fInputBufferReader = null;
74 private BufferedReader fErrorBufferReader = null;
ea21cd65 75 private final ExecutorService fExecutor = Executors.newFixedThreadPool(1);
eb1bab5b 76 private boolean fIsConnected = false;
ea21cd65 77 private final Random fRandom = new Random(System.currentTimeMillis());
d6fc6e1b 78 private int fReturnValue;
ea21cd65 79
eb1bab5b
BH
80 // ------------------------------------------------------------------------
81 // Constructors
82 // ------------------------------------------------------------------------
ea21cd65
AM
83
84 /**
85 * Create a new command shell
86 *
87 * @param proxy
88 * The RSE proxy for this shell
89 */
eb1bab5b
BH
90 public CommandShell(IRemoteSystemProxy proxy) {
91 fProxy = proxy;
92 }
93
94 // ------------------------------------------------------------------------
95 // Operations
96 // ------------------------------------------------------------------------
97 /*
98 * (non-Javadoc)
115b4a01 99 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#connect()
eb1bab5b
BH
100 */
101 @Override
102 public void connect() throws ExecutionException {
103 IShellService shellService = fProxy.getShellService();
104 Process p = null;
105 try {
106 fHostShell = shellService.launchShell("", new String[0], new NullProgressMonitor()); //$NON-NLS-1$
107 p = new HostShellProcessAdapter(fHostShell);
108 } catch (Exception e) {
109 throw new ExecutionException(Messages.TraceControl_CommandShellError, e);
110 }
d6fc6e1b
BH
111 fInputBufferReader = new BufferedReader(new InputStreamReader(p.getInputStream()));
112 fErrorBufferReader = new BufferedReader(new InputStreamReader(p.getErrorStream()));
eb1bab5b 113 fIsConnected = true;
eb1bab5b
BH
114 }
115
116 /*
117 * (non-Javadoc)
115b4a01 118 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#disconnect()
eb1bab5b
BH
119 */
120 @Override
121 public void disconnect() {
122 fIsConnected = false;
123 try {
d6fc6e1b
BH
124 fInputBufferReader.close();
125 fErrorBufferReader.close();
eb1bab5b
BH
126 } catch (IOException e) {
127 // ignore
128 }
129 }
130
131 /*
132 * (non-Javadoc)
115b4a01 133 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor)
eb1bab5b
BH
134 */
135 @Override
136 public ICommandResult executeCommand(String command, IProgressMonitor monitor) throws ExecutionException {
137 return executeCommand(command, monitor, true);
138 }
139
140 /*
141 * (non-Javadoc)
115b4a01 142 * @see org.eclipse.linuxtools.internal.lttng2.ui.views.control.service.ICommandShell#executeCommand(java.lang.String, org.eclipse.core.runtime.IProgressMonitor, boolean)
eb1bab5b
BH
143 */
144 @Override
145 public ICommandResult executeCommand(final String command, final IProgressMonitor monitor, final boolean checkReturnValue) throws ExecutionException {
146 if (fIsConnected) {
147 FutureTask<CommandResult> future = new FutureTask<CommandResult>(new Callable<CommandResult>() {
148 @Override
149 public CommandResult call() throws IOException, CancellationException {
150 final ArrayList<String> result = new ArrayList<String>();
ea21cd65 151
eb1bab5b 152 synchronized (fHostShell) {
d6fc6e1b
BH
153 // Initialize return value which will be updated in isAliasEchoResult()
154 fReturnValue = 0;
155
156 int startAlias = fRandom.nextInt();
157 int endAlias = fRandom.nextInt();
158 fHostShell.writeToShell(formatShellCommand(command, startAlias, endAlias));
ea21cd65 159
eb1bab5b 160 String nextLine;
d6fc6e1b
BH
161 boolean isStartFound = false;
162 while ((nextLine = fInputBufferReader.readLine()) != null) {
eb1bab5b
BH
163
164 if (monitor.isCanceled()) {
165 flushInput();
ea21cd65 166 throw new CancellationException();
eb1bab5b
BH
167 }
168
ea21cd65 169 // check if line contains echoed start alias
d6fc6e1b
BH
170 if (isAliasEchoResult(nextLine, startAlias, true)) {
171 isStartFound = true;
172 continue;
eb1bab5b 173 }
eb1bab5b 174
ea21cd65
AM
175 // check if line contains is the end mark-up. This will retrieve also
176 // the return value of the actual command.
d6fc6e1b
BH
177 if (isAliasEchoResult(nextLine, endAlias, false)) {
178 break;
eb1bab5b
BH
179 }
180
d6fc6e1b
BH
181 // Ignore line if
182 // 1) start hasn't been found or
183 // 2) line is an echo of the command or
184 // 3) line is an echo of the end mark-up
185 if (!isStartFound ||
186 isCommandEcho(nextLine, command) ||
187 nextLine.contains(getEchoResult(endAlias)))
188 {
189 continue;
eb1bab5b 190 }
d6fc6e1b
BH
191
192 // Now it's time add to the result
193 result.add(nextLine);
eb1bab5b
BH
194 }
195
ea21cd65 196 // Read any left over output
eb1bab5b 197 flushInput();
ea21cd65 198
d6fc6e1b 199 // Read error stream output when command failed.
ea21cd65 200 if (fReturnValue != 0) {
d6fc6e1b
BH
201 while(fErrorBufferReader.ready()) {
202 if ((nextLine = fErrorBufferReader.readLine()) != null) {
203 result.add(nextLine);
204 }
205 }
206 }
eb1bab5b 207 }
d6fc6e1b 208 return new CommandResult(fReturnValue, result.toArray(new String[result.size()]));
eb1bab5b
BH
209 }
210 });
211
212 fExecutor.execute(future);
213
214 try {
215 return future.get(DEFAULT_TIMEOUT_VALUE, TimeUnit.MILLISECONDS);
216 } catch (java.util.concurrent.ExecutionException ex) {
217 throw new ExecutionException(Messages.TraceControl_ExecutionFailure, ex);
218 } catch (InterruptedException ex) {
219 throw new ExecutionException(Messages.TraceControl_ExecutionCancelled, ex);
220 } catch (TimeoutException ex) {
221 throw new ExecutionException(Messages.TraceControl_ExecutionTimeout, ex);
222 }
223 }
224 throw new ExecutionException(Messages.TraceControl_ShellNotConnected, null);
225 }
cfdb727a 226
eb1bab5b
BH
227 // ------------------------------------------------------------------------
228 // Helper methods
229 // ------------------------------------------------------------------------
230 /**
cfdb727a 231 * Flushes the buffer reader
eb1bab5b
BH
232 * @throws IOException
233 */
234 private void flushInput() throws IOException {
235 char[] cbuf = new char[1];
d6fc6e1b
BH
236 while (fInputBufferReader.ready()) {
237 if (fInputBufferReader.read(cbuf, 0, 1) == -1) {
eb1bab5b
BH
238 break;
239 }
240 }
241 }
cfdb727a 242
eb1bab5b 243 /**
d6fc6e1b 244 * Format the command to be sent into the shell command with start and end marker strings.
ea21cd65 245 * The start marker is need to know when the actual command output starts. The end marker
d6fc6e1b 246 * string is needed so we can tell that end of output has been reached.
ea21cd65
AM
247 *
248 * @param cmd The actual command
d6fc6e1b
BH
249 * @param startAlias The command alias for start marker
250 * @param endAlias The command alias for end marker
eb1bab5b
BH
251 * @return formatted command string
252 */
ea21cd65 253 private static String formatShellCommand(String cmd, int startAlias, int endAlias) {
cfdb727a 254 if (cmd == null || cmd.equals("")) { //$NON-NLS-1$
eb1bab5b 255 return cmd;
cfdb727a 256 }
eb1bab5b 257 StringBuffer formattedCommand = new StringBuffer();
ea21cd65 258 // Make multi-line command.
d6fc6e1b
BH
259 // Wrap actual command with start marker and end marker to wrap actual command.
260 formattedCommand.append(getEchoCmd(startAlias));
261 formattedCommand.append(CMD_DELIMITER);
262 formattedCommand.append(cmd);
263 formattedCommand.append(CMD_DELIMITER);
264 formattedCommand.append(getEchoCmd(endAlias));
eb1bab5b
BH
265 formattedCommand.append(CMD_DELIMITER);
266 return formattedCommand.toString();
267 }
ea21cd65 268
d6fc6e1b
BH
269 /**
270 * Creates a echo command line in the format: echo <start tag> <alias> <end tag> $?
ea21cd65 271 *
d6fc6e1b
BH
272 * @param alias The command alias integer to be included in the echoed message.
273 * @return the echo command line
274 */
ea21cd65 275 private static String getEchoCmd(int alias) {
d6fc6e1b
BH
276 return SHELL_ECHO_CMD + getEchoResult(alias) + "$?"; //$NON-NLS-1$
277 }
278
279 /**
ea21cd65 280 * Creates the expected result for a given command alias:
d6fc6e1b 281 * <start tag> <alias> <end tag> $?
ea21cd65 282 *
d6fc6e1b
BH
283 * @param alias The command alias integer to be included in the echoed message.
284 * @return the expected echo result
285 */
ea21cd65 286 private static String getEchoResult(int alias) {
d6fc6e1b
BH
287 return BEGIN_END_TAG + String.valueOf(alias) + DONE_MARKUP_STRING;
288 }
ea21cd65 289
d6fc6e1b
BH
290 /**
291 * Verifies if given command line contains a command alias echo result.
ea21cd65 292 *
d6fc6e1b
BH
293 * @param line The output line to test.
294 * @param alias The command alias
295 * @param checkReturnValue <code>true</code> to retrieve command result (previous command) <code>false</code>
296 * @return <code>true</code> if output line is a command alias echo result else <code>false</code>
297 */
298 private boolean isAliasEchoResult(String line, int alias, boolean checkReturnValue) {
299 String expected = getEchoResult(alias);
300 if (line.startsWith(expected)) {
301 if (!checkReturnValue) {
302 try {
303 int k = Integer.valueOf(line.substring(expected.length()));
304 fReturnValue = k;
305 } catch (NumberFormatException e) {
306 // do nothing
307 }
308 }
309 return true;
ea21cd65
AM
310 }
311 int index = line.indexOf(expected);
312 if (index > 0) {
313 if (line.indexOf(SHELL_ECHO_CMD) == -1) {
314 return true;
d6fc6e1b
BH
315 }
316 }
eb1bab5b 317
d6fc6e1b
BH
318 return false;
319 }
ea21cd65 320
d6fc6e1b 321 /**
ea21cd65
AM
322 * Verifies if output line is an echo of the given command line. If the
323 * output line is longer then the maximum line lengths (e.g. for ssh), the
324 * shell adds a line break character. This method takes this in
325 * consideration by comparing the command line without any whitespaces.
326 *
327 * @param line
328 * The output line to verify
329 * @param cmd
330 * The command executed
331 * @return <code>true</code> if it's an echoed command line else
332 * <code>false</code>
d6fc6e1b
BH
333 */
334 @SuppressWarnings("nls")
ea21cd65 335 private static boolean isCommandEcho(String line, String cmd) {
d6fc6e1b
BH
336 String s1 = line.replaceAll("\\s","");
337 String s2 = cmd.replaceAll("\\s","");
ea21cd65 338 s2 = s2.replaceAll("(\\*)", "(\\\\*)");
d6fc6e1b
BH
339 String patternStr = ".*(" + s2 +")$";
340 return s1.matches(patternStr);
341 }
eb1bab5b 342}
This page took 0.049815 seconds and 5 git commands to generate.