KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > continuent > sequoia > controller > backup > backupers > NativeCommandExec


1 /**
2  * Sequoia: Database clustering technology.
3  * Copyright (C) 2006 Continuent, Inc.
4  * Contact: sequoia@continuent.org
5  *
6  * Licensed under the Apache License, Version 2.0 (the "License");
7  * you may not use this file except in compliance with the License.
8  * You may obtain a copy of the License at
9  *
10  * http://www.apache.org/licenses/LICENSE-2.0
11  *
12  * Unless required by applicable law or agreed to in writing, software
13  * distributed under the License is distributed on an "AS IS" BASIS,
14  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  * See the License for the specific language governing permissions and
16  * limitations under the License.
17  *
18  * Initial developer(s): Emmanuel Cecchet.
19  * Contributor(s): ______________________.
20  */

21
22 package org.continuent.sequoia.controller.backup.backupers;
23
24 import java.io.BufferedWriter JavaDoc;
25 import java.io.File JavaDoc;
26 import java.io.IOException JavaDoc;
27 import java.io.OutputStream JavaDoc;
28 import java.io.StringWriter JavaDoc;
29 import java.util.ArrayList JavaDoc;
30 import java.util.Timer JavaDoc;
31 import java.util.TimerTask JavaDoc;
32
33 import org.continuent.sequoia.common.log.Trace;
34
35 /**
36  * This class defines a NativeCommandExec, which abstracts out native command
37  * execution logic into a small and easily verified class that does not have
38  * dependencies on backuper implementations. Once instantiated this class can
39  * execute a series of commands in sequence.
40  *
41  * @author <a HREF="mailto:robert.hodges@continuent.com">Robert Hodges</a>
42  * @version 1.0
43  */

44 public class NativeCommandExec
45 {
46   /** Maximum milliseconds to wait for output threads to complete. */
47   private static final int THREAD_WAIT_MAX = 30 * 1000;
48
49   // Development logger
50
private static Trace logger = Trace
51                                                .getLogger(NativeCommandExec.class
52                                                    .getName());
53
54   // Output from most recent command execution.
55
ArrayList JavaDoc stdout;
56   ArrayList JavaDoc stderr;
57
58   // Defines a class to hold native commands, be they array or a simple string.
59
class NativeCommand
60   {
61     String JavaDoc command;
62     String JavaDoc[] commands;
63
64     // Creates a native command based on a string.
65
NativeCommand(String JavaDoc cmd)
66     {
67       this.command = cmd;
68     }
69
70     // Creates a native command based on an array.
71
NativeCommand(String JavaDoc[] cmds)
72     {
73       this.commands = cmds;
74     }
75
76     // Return true if this command is a string rather than an array.
77
boolean isString()
78     {
79       return (command != null);
80     }
81
82     // Return the command text in form suitable for error messages.
83
String JavaDoc commandText()
84     {
85       if (isString())
86         return command;
87       else
88       {
89         // Concatenate commands for logging purposes.
90
StringBuffer JavaDoc buf = new StringBuffer JavaDoc();
91         for (int i = 0; i < commands.length; i++)
92         {
93           buf.append(commands[i] + " ");
94         }
95         return buf.toString();
96       }
97     }
98
99     // Return the command string or null if this is an array command.
100
String JavaDoc getCommand()
101     {
102       return command;
103     }
104
105     // Return the command array or null if this is a string command.
106
String JavaDoc[] getCommands()
107     {
108       return commands;
109     }
110   }
111
112   /**
113    * Creates a new <code>NativeCommandExec</code> object
114    */

115   public NativeCommandExec()
116   {
117   }
118
119   /**
120    * Returns all stdout from the most recent command.
121    *
122    * @return stdout contents (may be truncated)
123    */

124   public ArrayList JavaDoc getStdout()
125   {
126     return stdout;
127   }
128
129   /**
130    * Returns all stderr from the most recent command.
131    *
132    * @return stderr contents (may be truncated)
133    */

134   public ArrayList JavaDoc getStderr()
135   {
136     return stderr;
137   }
138
139   /**
140    * Clears output arrays.
141    */

142   public void initOutput()
143   {
144     stdout = new ArrayList JavaDoc();
145     stderr = new ArrayList JavaDoc();
146   }
147
148   /**
149    * Utility method to execute a native command with careful logging and
150    * coverage of all command outcomes.
151    *
152    * @param cmd Command string
153    * @param input Array containing lines of input
154    * @param output Optional output stream to catch data written to standard
155    * output
156    * @param timeout Timeout in seconds (0 = infinite)
157    * @param workingDirectory working directory for the command (null to inherit
158    * from Sequoia working dir)
159    * @param ignoreStdErrOutput true if output on std error should be ignored,
160    * else any output on stderr will be considered as a failure
161    * @return true if command is successful
162    */

163   public boolean safelyExecNativeCommand(String JavaDoc cmd,
164       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
165       File JavaDoc workingDirectory, boolean ignoreStdErrOutput)
166   {
167     NativeCommand nc = new NativeCommand(cmd);
168     return safelyExecNativeCommand0(nc, input, output, timeout,
169         workingDirectory, ignoreStdErrOutput);
170   }
171
172   /**
173    * @see #safelyExecNativeCommand(String, NativeCommandInputSource,
174    * OutputStream, int, File)
175    */

176   public boolean safelyExecNativeCommand(String JavaDoc cmd,
177       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
178       boolean ignoreStdErrOutput)
179   {
180     return safelyExecNativeCommand(cmd, input, output, timeout, null,
181         ignoreStdErrOutput);
182   }
183
184   /**
185    * Utility method to execute a native command with careful logging and
186    * coverage of all command outcomes.
187    *
188    * @param cmds Command array
189    * @param input Input source for native command
190    * @param output Optional output stream to catch data written to standard
191    * output
192    * @param timeout Timeout in seconds (0 = infinite)
193    * @param workingDirectory working directory for the command (null to inherit
194    * from Sequoia working dir)
195    * @param ignoreStdErrOutput true if output on std error should be ignored,
196    * else any output on stderr will be considered as a failure
197    * @return true if command is successful
198    */

199   public boolean safelyExecNativeCommand(String JavaDoc[] cmds,
200       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
201       File JavaDoc workingDirectory, boolean ignoreStdErrOutput)
202   {
203     NativeCommand nc = new NativeCommand(cmds);
204     return safelyExecNativeCommand0(nc, input, output, timeout,
205         workingDirectory, ignoreStdErrOutput);
206   }
207
208   /**
209    * @see #safelyExecNativeCommand(String[], NativeCommandInputSource,
210    * OutputStream, int, File)
211    */

212   public boolean safelyExecNativeCommand(String JavaDoc[] cmds,
213       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
214       boolean ignoreStdErrOutput)
215   {
216     return safelyExecNativeCommand(cmds, input, output, timeout, null,
217         ignoreStdErrOutput);
218   }
219
220   /**
221    * Internal method to execute a native command with careful logging and
222    * coverage of all command outcomes. This method among other things logs
223    * output fully in the event of a failure.
224    *
225    * @param nc Command holder object
226    * @param input Command input source
227    * @param output Optional output stream to catch data written to standard
228    * output
229    * @param timeout Timeout in seconds (0 = infinite)
230    * @param workingDirectory working directory for the command (null to inherit
231    * from Sequoia working dir)
232    * @param ignoreStdErrOutput true if output on std error should be ignored,
233    * else any output on stderr will be considered as a failure
234    * @return true if command is successful
235    */

236   protected boolean safelyExecNativeCommand0(NativeCommand nc,
237       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
238       File JavaDoc workingDirectory, boolean ignoreStdErrOutput)
239   {
240     boolean status;
241     try
242     {
243       int errorCount;
244       if (nc.isString())
245         errorCount = executeNativeCommand(nc.getCommand(), input, output,
246             timeout, workingDirectory, ignoreStdErrOutput);
247       else
248         errorCount = executeNativeCommand(nc.getCommands(), input, output,
249             timeout, workingDirectory, ignoreStdErrOutput);
250       status = (errorCount == 0);
251       if (!status)
252         logger.warn("Native command failed with error count=" + errorCount);
253     }
254     catch (InterruptedException JavaDoc e)
255     {
256       logger.warn("Native command timed out: command=" + nc.commandText()
257           + " timeout=" + timeout);
258       status = false;
259     }
260     catch (IOException JavaDoc e)
261     {
262       logger.warn("Native command I/O failed: command=" + nc.commandText(), e);
263       status = false;
264     }
265
266     // If status is false, dump all output to the development log.
267
if (status == false)
268     {
269       logOutput();
270       logErrors();
271     }
272
273     return status;
274   }
275
276   /**
277    * Manages execution of a command once it has been started as a process.
278    *
279    * @param commandText The command text for logging messages
280    * @param process The process object used to manage the command
281    * @param inputSource Input source object or null if no input
282    * @param output Optional output stream to catch standard out
283    * @param timeout Time in seconds to await command (0 = forever)
284    * @param ignoreStdErrOutput true if output on std error should be ignored,
285    * else any output on stderr will be considered as a failure
286    * @return 0 if successful, any number otherwise
287    * @throws InterruptedException If there is a timeout failure
288    */

289   protected int manageCommandExecution(String JavaDoc commandText, Process JavaDoc process,
290       NativeCommandInputSource inputSource, OutputStream JavaDoc output, int timeout,
291       boolean ignoreStdErrOutput) throws InterruptedException JavaDoc
292   {
293     if (logger.isInfoEnabled())
294     {
295       logger.info("Starting execution of \"" + commandText + "\"");
296     }
297
298     // Spawn 2 threads to capture the process output, prevents blocking
299
NativeCommandOutputThread inStreamThread = new NativeCommandOutputThread(
300         process.getInputStream(), output);
301     NativeCommandOutputThread errStreamThread = new NativeCommandOutputThread(
302         process.getErrorStream(), null);
303     inStreamThread.start();
304     errStreamThread.start();
305
306     // Manage command execution.
307
TimerTask JavaDoc task = null;
308     try
309     {
310       // Schedule the timer if we need a timeout.
311
if (timeout > 0)
312       {
313         final Thread JavaDoc t = Thread.currentThread();
314         task = new TimerTask JavaDoc()
315         {
316           public void run()
317           {
318             t.interrupt();
319           }
320         };
321         Timer JavaDoc timer = new Timer JavaDoc();
322         timer.schedule(task, timeout * 1000);
323       }
324
325       // Provide standard input if present.
326
if (inputSource != null)
327       {
328         OutputStream JavaDoc processOutput = process.getOutputStream();
329         try
330         {
331           inputSource.write(processOutput);
332         }
333         catch (IOException JavaDoc e)
334         {
335           logger.warn("Writing of data to stdin halted by exception", e);
336         }
337         finally
338         {
339           try
340           {
341             inputSource.close();
342           }
343           catch (IOException JavaDoc e)
344           {
345             logger.warn("Input source close operation generated exception", e);
346           }
347           try
348           {
349             processOutput.close();
350           }
351           catch (IOException JavaDoc e)
352           {
353             logger.warn("Process stdin close operation generated exception", e);
354           }
355         }
356       }
357
358       // Wait for process to complete.
359
process.waitFor();
360
361       // Wait for threads to complete. Not strictly necessary
362
// but makes it more likely we will read output properly.
363
inStreamThread.join(THREAD_WAIT_MAX);
364       errStreamThread.join(THREAD_WAIT_MAX);
365     }
366     catch (InterruptedException JavaDoc e)
367     {
368       logger.warn("Command exceeded timeout: " + commandText);
369       process.destroy();
370       throw e;
371     }
372     finally
373     {
374       // Cancel the timer and cleanup process stdin.
375
if (task != null)
376         task.cancel();
377
378       // Collect output--this needs to happen no matter what.
379
stderr = errStreamThread.getOutput();
380       stdout = inStreamThread.getOutput();
381     }
382
383     if (logger.isInfoEnabled())
384     {
385       logger.info("Command \"" + commandText + "\" logged " + stderr.size()
386           + " errors and terminated with exitcode " + process.exitValue());
387     }
388
389     if (ignoreStdErrOutput)
390       return process.exitValue();
391     else
392     {
393       if (stderr.size() > 0) // Return non-zero code if errors on stderr
394
return -1;
395       else
396         return process.exitValue();
397     }
398   }
399
400   /**
401    * Executes a native operating system command expressed as a String.
402    *
403    * @param command String of command to execute
404    * @param input Native command input
405    * @param output Optional output to catch standard out
406    * @param timeout Time in seconds to await command (0 = forever)
407    * @param workingDirectory working directory for the command (null to inherit
408    * from Sequoia working dir)
409    * @param ignoreStdErrOutput true if output on std error should be ignored,
410    * else any output on stderr will be considered as a failure
411    * @return 0 if successful, any number otherwise
412    * @exception IOException if an I/O error occurs
413    * @throws InterruptedException If there is a timeout failure
414    */

415   public int executeNativeCommand(String JavaDoc command,
416       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
417       File JavaDoc workingDirectory, boolean ignoreStdErrOutput) throws IOException JavaDoc,
418       InterruptedException JavaDoc
419   {
420     initOutput();
421     Process JavaDoc process = Runtime.getRuntime()
422         .exec(command, null, workingDirectory);
423     return manageCommandExecution(command, process, input, output, timeout,
424         ignoreStdErrOutput);
425   }
426
427   /**
428    * @see #executeNativeCommand(String, NativeCommandInputSource, OutputStream,
429    * int, File)
430    */

431   public int executeNativeCommand(String JavaDoc command,
432       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
433       boolean ignoreStdErrOutput) throws IOException JavaDoc, InterruptedException JavaDoc
434   {
435     return executeNativeCommand(command, input, output, timeout, null,
436         ignoreStdErrOutput);
437   }
438
439   /**
440    * Executes a native operating system command expressed as an array.
441    *
442    * @param commands Array of strings (command + args) to execute
443    * @param input Native command input
444    * @param output Command output stream
445    * @param timeout Time in seconds to await command (0 = forever)
446    * @param workingDirectory working directory for the command (null to inherit
447    * from Sequoia working dir)
448    * @param ignoreStdErrOutput true if output on std error should be ignored,
449    * else any output on stderr will be considered as a failure
450    * @return 0 if successful, any number otherwise
451    * @exception IOException if an I/O error occurs
452    * @throws InterruptedException If there is a timeout failure
453    */

454   public int executeNativeCommand(String JavaDoc[] commands,
455       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
456       File JavaDoc workingDirectory, boolean ignoreStdErrOutput) throws IOException JavaDoc,
457       InterruptedException JavaDoc
458   {
459     initOutput();
460     Process JavaDoc process = Runtime.getRuntime().exec(commands, null,
461         workingDirectory);
462     return manageCommandExecution(new NativeCommand(commands).commandText(),
463         process, input, output, timeout, ignoreStdErrOutput);
464   }
465
466   /**
467    * @see #executeNativeCommand(String[], NativeCommandInputSource,
468    * OutputStream, int, File)
469    */

470   public int executeNativeCommand(String JavaDoc[] commands,
471       NativeCommandInputSource input, OutputStream JavaDoc output, int timeout,
472       boolean ignoreStdErrOutput) throws IOException JavaDoc, InterruptedException JavaDoc
473   {
474     return executeNativeCommand(commands, input, output, timeout, null,
475         ignoreStdErrOutput);
476   }
477
478   /**
479    * Write process standard output to development log.
480    */

481   public void logOutput()
482   {
483     log("stdout", stdout);
484   }
485
486   /**
487    * Write process error output to development log.
488    */

489   public void logErrors()
490   {
491     log("stderr", stderr);
492   }
493
494   /**
495    * Utility routine to log output.
496    *
497    * @param name Output type e.g., stdout or stderr
498    * @param outArray List of lines of output
499    */

500   protected void log(String JavaDoc name, ArrayList JavaDoc outArray)
501   {
502     StringWriter JavaDoc sw = new StringWriter JavaDoc();
503     BufferedWriter JavaDoc writer = new BufferedWriter JavaDoc(sw);
504     if (outArray != null)
505     {
506       int arraySize = outArray.size();
507       for (int i = 0; i < arraySize; i++)
508       {
509         String JavaDoc line = (String JavaDoc) outArray.get(i);
510         try
511         {
512           writer.newLine();
513           writer.write(line);
514         }
515         catch (IOException JavaDoc e)
516         {
517           // This would be very unexpected when writing to a string.
518
logger.error(
519               "Unexpected exception while trying to log process output", e);
520         }
521       }
522     }
523
524     // Flush out collected output.
525
try
526     {
527       writer.flush();
528       writer.close();
529     }
530     catch (IOException JavaDoc e)
531     {
532     }
533
534     String JavaDoc outText = sw.toString();
535     logger.info("Process output (" + name + "):" + outText);
536   }
537 }
Popular Tags