KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > alfresco > util > exec > RuntimeExec


1 /*
2  * Copyright (C) 2005 Alfresco, Inc.
3  *
4  * Licensed under the Mozilla Public License version 1.1
5  * with a permitted attribution clause. You may obtain a
6  * copy of the License at
7  *
8  * http://www.alfresco.org/legal/license.txt
9  *
10  * Unless required by applicable law or agreed to in writing,
11  * software distributed under the License is distributed on an
12  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13  * either express or implied. See the License for the specific
14  * language governing permissions and limitations under the
15  * License.
16  */

17 package org.alfresco.util.exec;
18
19 import java.io.BufferedInputStream JavaDoc;
20 import java.io.IOException JavaDoc;
21 import java.io.InputStream JavaDoc;
22 import java.util.Collections JavaDoc;
23 import java.util.HashMap JavaDoc;
24 import java.util.HashSet JavaDoc;
25 import java.util.Map JavaDoc;
26 import java.util.Set JavaDoc;
27 import java.util.StringTokenizer JavaDoc;
28
29 import org.alfresco.error.AlfrescoRuntimeException;
30 import org.apache.commons.logging.Log;
31 import org.apache.commons.logging.LogFactory;
32
33 /**
34  * This acts as a session similar to the <code>java.lang.Process</code>, but
35  * logs the system standard and error streams.
36  * <p>
37  * The bean can be configured to execute a command directly, or be given a map
38  * of commands keyed by the <i>os.name</i> Java system property. In this map,
39  * the default key that is used when no match is found is the
40  * <b>{@link #KEY_OS_DEFAULT *}</b> key.
41  * <p>
42  * Commands may use placeholders, e.g.
43  * <pre><code>
44  * find -name ${filename}
45  * </code></pre>
46  * The <b>filename</b> property will be substituted for any supplied value prior to
47  * each execution of the command. Currently, no checks are made to get or check the
48  * properties contained within the command string. It is up to the client code to
49  * dynamically extract the properties required if the required properties are not
50  * known up front.
51  *
52  * @author Derek Hulley
53  */

54 public class RuntimeExec
55 {
56     /** the key to use when specifying a command for any other OS: <b>*</b> */
57     public static final String JavaDoc KEY_OS_DEFAULT = "*";
58     
59     private static final String JavaDoc KEY_OS_NAME = "os.name";
60     private static final int BUFFER_SIZE = 1024;
61     private static final String JavaDoc VAR_OPEN = "${";
62     private static final String JavaDoc VAR_CLOSE = "}";
63
64     private static Log logger = LogFactory.getLog(RuntimeExec.class);
65
66     private String JavaDoc command;
67     private Map JavaDoc<String JavaDoc, String JavaDoc> defaultProperties;
68     private Set JavaDoc<Integer JavaDoc> errCodes;
69
70     /**
71      * Default constructor. Initialize this instance by setting individual properties.
72      */

73     public RuntimeExec()
74     {
75         defaultProperties = Collections.emptyMap();
76         // set default error codes
77
this.errCodes = new HashSet JavaDoc<Integer JavaDoc>(2);
78         errCodes.add(1);
79         errCodes.add(2);
80     }
81     
82     /**
83      * Set the command to execute regardless of operating system
84      *
85      * @param command the command string
86      */

87     public void setCommand(String JavaDoc command)
88     {
89         this.command = command;
90     }
91     
92     /**
93      * Supply a choice of commands to execute based on a mapping from the <i>os.name</i> system
94      * property to the command to execute. The {@link #KEY_OS_DEFAULT *} key can be used
95      * to get a command where there is not direct match to the operating system key.
96      *
97      * @param commandsByOS a map of command string keyed by operating system names
98      */

99     public void setCommandMap(Map JavaDoc<String JavaDoc, String JavaDoc> commandsByOS)
100     {
101         // get the current OS
102
String JavaDoc serverOs = System.getProperty(KEY_OS_NAME);
103         // attempt to find a match
104
String JavaDoc command = commandsByOS.get(serverOs);
105         if (command == null)
106         {
107             // go through the commands keys, looking for one that matches by regular expression matching
108
for (String JavaDoc osName : commandsByOS.keySet())
109             {
110                 // Ignore * options. It is dealt with later.
111
if (osName.equals(KEY_OS_DEFAULT))
112                 {
113                     continue;
114                 }
115                 // Do regex match
116
if (serverOs.matches(osName))
117                 {
118                     command = commandsByOS.get(osName);
119                     break;
120                 }
121             }
122         }
123         // check
124
if (command == null)
125         {
126             throw new AlfrescoRuntimeException(
127                     "No command found for OS " + serverOs + " or '" + KEY_OS_DEFAULT + "': \n" +
128                     " commands: " + commandsByOS);
129         }
130         this.command = command;
131     }
132     
133     /**
134      * Set the default properties to use when executing the command. The properties
135      * supplied during execution will overwrite the default properties.
136      * <p>
137      * <code>null</code> properties will be treated as an empty string for substitution
138      * purposes.
139      *
140      * @param defaultProperties property values
141      */

142     public void setDefaultProperties(Map JavaDoc<String JavaDoc, String JavaDoc> defaultProperties)
143     {
144         this.defaultProperties = defaultProperties;
145     }
146     
147     /**
148      * A comma or space separated list of values that, if returned by the executed command,
149      * indicate an error value. This defaults to <b>"1, 2"</b>.
150      *
151      * @param erroCodesStr the error codes for the execution
152      */

153     public void setErrorCodes(String JavaDoc errCodesStr)
154     {
155         errCodes.clear();
156         StringTokenizer JavaDoc tokenizer = new StringTokenizer JavaDoc(errCodesStr, " ,");
157         while(tokenizer.hasMoreElements())
158         {
159             String JavaDoc errCodeStr = tokenizer.nextToken();
160             // attempt to convert it to an integer
161
try
162             {
163                 int errCode = Integer.parseInt(errCodeStr);
164                 this.errCodes.add(errCode);
165             }
166             catch (NumberFormatException JavaDoc e)
167             {
168                 throw new AlfrescoRuntimeException(
169                         "Property 'errorCodes' must be comma-separated list of integers: " + errCodesStr);
170             }
171         }
172     }
173     
174     /**
175      * Executes the command using the default properties
176      *
177      * @see #execute(Map)
178      */

179     public ExecutionResult execute()
180     {
181         return execute(defaultProperties);
182     }
183
184     /**
185      * Executes the statement that this instance was constructed with.
186      * <p>
187      * <code>null</code> properties will be treated as an empty string for substitution
188      * purposes.
189      *
190      * @return Returns the full execution results
191      */

192     public ExecutionResult execute(Map JavaDoc<String JavaDoc, String JavaDoc> properties)
193     {
194         // check that the command has been set
195
if (command == null)
196         {
197             throw new AlfrescoRuntimeException("Runtime command has not been set: \n" + this);
198         }
199         
200         // create the properties
201
Runtime JavaDoc runtime = Runtime.getRuntime();
202         Process JavaDoc process = null;
203         String JavaDoc commandToExecute = null;
204         try
205         {
206             // execute the command with full property replacement
207
commandToExecute = getCommand(properties);
208             process = runtime.exec(commandToExecute);
209         }
210         catch (IOException JavaDoc e)
211         {
212             throw new AlfrescoRuntimeException("Failed to execute command: " + commandToExecute, e);
213         }
214
215         // create the stream gobblers
216
InputStreamReaderThread stdOutGobbler = new InputStreamReaderThread(process.getInputStream());
217         InputStreamReaderThread stdErrGobbler = new InputStreamReaderThread(process.getErrorStream());
218
219         // start gobbling
220
stdOutGobbler.start();
221         stdErrGobbler.start();
222
223         // wait for the process to finish
224
int exitValue = 0;
225         try
226         {
227             exitValue = process.waitFor();
228         }
229         catch (InterruptedException JavaDoc e)
230         {
231             // process was interrupted - generate an error message
232
stdErrGobbler.addToBuffer(e.toString());
233             exitValue = 1;
234         }
235
236         // ensure that the stream gobblers get to finish
237
stdOutGobbler.waitForCompletion();
238         stdErrGobbler.waitForCompletion();
239
240         // get the stream values
241
String JavaDoc execOut = stdOutGobbler.getBuffer();
242         String JavaDoc execErr = stdErrGobbler.getBuffer();
243         
244         // construct the return value
245
ExecutionResult result = new ExecutionResult(commandToExecute, errCodes, exitValue, execOut, execErr);
246
247         // done
248
if (logger.isDebugEnabled())
249         {
250             logger.debug(result);
251         }
252         return result;
253     }
254
255     /**
256      * @return Returns the command that will be executed if no additional properties
257      * were to be supplied
258      */

259     public String JavaDoc getCommand()
260     {
261         return getCommand(defaultProperties);
262     }
263     
264     /**
265      * Get the command that will be executed post substitution.
266      * <p>
267      * <code>null</code> properties will be treated as an empty string for substitution
268      * purposes.
269      *
270      * @param properties the properties that might be executed with
271      * @return Returns the command that will be executed should the additional properties
272      * be supplied
273      */

274     public String JavaDoc getCommand(Map JavaDoc<String JavaDoc, String JavaDoc> properties)
275     {
276         Map JavaDoc<String JavaDoc, String JavaDoc> execProperties = null;
277         if (properties == defaultProperties)
278         {
279             // we are just using the default properties
280
execProperties = defaultProperties;
281         }
282         else
283         {
284             execProperties = new HashMap JavaDoc<String JavaDoc, String JavaDoc>(defaultProperties);
285             // overlay the supplied properties
286
execProperties.putAll(properties);
287         }
288         // perform the substitution
289
StringBuilder JavaDoc sb = new StringBuilder JavaDoc(command);
290         for (Map.Entry JavaDoc<String JavaDoc, String JavaDoc> entry : execProperties.entrySet())
291         {
292             String JavaDoc key = entry.getKey();
293             String JavaDoc value = entry.getValue();
294             // ignore null
295
if (value == null)
296             {
297                 value = "";
298             }
299             // progressively replace the property in the command
300
key = (VAR_OPEN + key + VAR_CLOSE);
301             int index = sb.indexOf(key);
302             while (index > -1)
303             {
304                 // replace
305
sb.replace(index, index + key.length(), value);
306                 // get the next one
307
index = sb.indexOf(key, index + 1);
308             }
309         }
310         // done
311
return sb.toString();
312     }
313     
314     public String JavaDoc toString()
315     {
316         StringBuffer JavaDoc sb = new StringBuffer JavaDoc(256);
317         sb.append("RuntimeExec:\n")
318           .append(" command: ").append(command).append("\n")
319           .append(" os: ").append(System.getProperty(KEY_OS_NAME)).append("\n");
320         return sb.toString();
321     }
322     
323     /**
324      * Object to carry the results of an execution to the caller.
325      *
326      * @author Derek Hulley
327      */

328     public static class ExecutionResult
329     {
330         private final String JavaDoc command;
331         private final Set JavaDoc<Integer JavaDoc> errCodes;
332         private final int exitValue;
333         private final String JavaDoc stdOut;
334         private final String JavaDoc stdErr;
335        
336         private ExecutionResult(
337                 final String JavaDoc command,
338                 final Set JavaDoc<Integer JavaDoc> errCodes,
339                 final int exitValue,
340                 final String JavaDoc stdOut,
341                 final String JavaDoc stdErr)
342         {
343             this.command = command;
344             this.errCodes = errCodes;
345             this.exitValue = exitValue;
346             this.stdOut = stdOut;
347             this.stdErr = stdErr;
348         }
349         
350         @Override JavaDoc
351         public String JavaDoc toString()
352         {
353             String JavaDoc out = stdOut.length() > 250 ? stdOut.substring(0, 250) : stdOut;
354             String JavaDoc err = stdErr.length() > 250 ? stdErr.substring(0, 250) : stdErr;
355             
356             StringBuilder JavaDoc sb = new StringBuilder JavaDoc(128);
357             sb.append("Execution result: \n")
358               .append(" os: ").append(System.getProperty(KEY_OS_NAME)).append("\n")
359               .append(" command: ").append(command).append("\n")
360               .append(" succeeded: ").append(getSuccess()).append("\n")
361               .append(" exit code: ").append(exitValue).append("\n")
362               .append(" out: ").append(out).append("\n")
363               .append(" err: ").append(err);
364             return sb.toString();
365         }
366         
367         /**
368          * @param exitValue the command exit value
369          * @return Returns true if the code is a listed failure code
370          *
371          * @see #setErrorCodes(String)
372          */

373         private boolean isFailureCode(int exitValue)
374         {
375             return errCodes.contains((Integer JavaDoc)exitValue);
376         }
377         
378         /**
379          * @return Returns true if the command was deemed to be successful according to the
380          * failure codes returned by the execution.
381          */

382         public boolean getSuccess()
383         {
384             return !isFailureCode(exitValue);
385         }
386
387         public int getExitValue()
388         {
389             return exitValue;
390         }
391         
392         public String JavaDoc getStdOut()
393         {
394             return stdOut;
395         }
396     
397         public String JavaDoc getStdErr()
398         {
399             return stdErr;
400         }
401     }
402
403     /**
404      * Gobbles an <code>InputStream</code> and writes it into a
405      * <code>StringBuffer</code>
406      * <p>
407      * The reading of the input stream is buffered.
408      */

409     public static class InputStreamReaderThread extends Thread JavaDoc
410     {
411         private InputStream JavaDoc is;
412         private StringBuffer JavaDoc buffer; // we require the synchronization
413
private boolean isRunning;
414         private boolean completed;
415
416         /**
417          * @param is an input stream to read - it will be wrapped in a buffer
418          * for reading
419          */

420         public InputStreamReaderThread(InputStream JavaDoc is)
421         {
422             super();
423             setDaemon(true); // must not hold up the VM if it is terminating
424
this.is = is;
425             this.buffer = new StringBuffer JavaDoc(BUFFER_SIZE);
426             this.isRunning = false;
427             this.completed = false;
428         }
429
430         public synchronized void run()
431         {
432             // mark this thread as running
433
isRunning = true;
434             completed = false;
435
436             byte[] bytes = new byte[BUFFER_SIZE];
437             InputStream JavaDoc tempIs = null;
438             try
439             {
440                 tempIs = new BufferedInputStream JavaDoc(is, BUFFER_SIZE);
441                 int count = -2;
442                 while (count != -1)
443                 {
444                     // do we have something previously read?
445
if (count > 0)
446                     {
447                         String JavaDoc toWrite = new String JavaDoc(bytes, 0, count);
448                         buffer.append(toWrite);
449                     }
450                     // read the next set of bytes
451
count = tempIs.read(bytes);
452                 }
453                 // done
454
isRunning = false;
455                 completed = true;
456             }
457             catch (IOException JavaDoc e)
458             {
459                 throw new AlfrescoRuntimeException("Unable to read stream", e);
460             }
461             finally
462             {
463                 // close the input stream
464
if (tempIs != null)
465                 {
466                     try
467                     {
468                         tempIs.close();
469                     }
470                     catch (Exception JavaDoc e)
471                     {
472                     }
473                 }
474             }
475         }
476
477         /**
478          * Waits for the run to complete.
479          * <p>
480          * <b>Remember to <code>start</code> the thread first
481          */

482         public synchronized void waitForCompletion()
483         {
484             while (!completed && !isRunning)
485             {
486                 try
487                 {
488                     // release our lock and wait a bit
489
this.wait(200L); // 200 ms
490
}
491                 catch (InterruptedException JavaDoc e)
492                 {
493                 }
494             }
495         }
496         
497         /**
498          * @param msg the message to add to the buffer
499          */

500         public void addToBuffer(String JavaDoc msg)
501         {
502             buffer.append(msg);
503         }
504
505         public boolean isComplete()
506         {
507             return completed;
508         }
509
510         /**
511          * @return Returns the current state of the buffer
512          */

513         public String JavaDoc getBuffer()
514         {
515             return buffer.toString();
516         }
517     }
518 }
519
Popular Tags