View Javadoc

1   package org.apache.helix.filestore;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.
20   */
21  
22  import java.io.BufferedInputStream;
23  import java.io.ByteArrayOutputStream;
24  import java.io.File;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.UnsupportedEncodingException;
28  import java.util.ArrayList;
29  import java.util.Arrays;
30  import java.util.List;
31  import java.util.Map;
32  import java.util.concurrent.TimeoutException;
33  
34  import org.apache.log4j.Logger;
35  
36  public class ExternalCommand
37  {
38    public static final String MODULE = ExternalCommand.class.getName();
39    public static final Logger LOG = Logger.getLogger(MODULE);
40  
41    private final ProcessBuilder _processBuilder;
42  
43    private Process _process;
44    private InputReader _out;
45    private InputReader _err;
46  
47    private static class InputReader extends Thread
48    {
49      private static final int BUFFER_SIZE = 2048;
50  
51      private final InputStream _in;
52      private final ByteArrayOutputStream _out;
53      private boolean _running = false;
54  
55      InputReader(InputStream in)
56      {
57        _in = in;
58        _out = new ByteArrayOutputStream();
59      }
60  
61      @Override
62      public void run()
63      {
64        _running = true;
65  
66        byte[] buf = new byte[BUFFER_SIZE];
67        int n = 0;
68        try
69        {
70          while((n = _in.read(buf)) != -1)
71            _out.write(buf, 0, n);
72        }
73        catch(IOException e)
74        {
75          LOG.error("error while reading external command", e);
76        }
77  
78        _running = false;
79      }
80  
81      public byte[] getOutput()
82      {
83        if(_running)
84          throw new IllegalStateException("wait for process to be completed");
85  
86        return _out.toByteArray();
87      }
88    }
89    /**
90  * Constructor */
91    public ExternalCommand(ProcessBuilder processBuilder)
92    {
93      _processBuilder = processBuilder;
94    }
95  
96    /**
97  * After creating the command, you have to start it...
98  *
99  * @throws IOException
100 */
101   public void start() throws IOException
102   {
103     _process = _processBuilder.start();
104     //System.out.println(_processBuilder);
105     _out = new InputReader(new BufferedInputStream(_process.getInputStream()));
106     _err = new InputReader(new BufferedInputStream(_process.getErrorStream()));
107 
108     _out.start();
109     _err.start();
110   }
111 
112   /**
113 * @see ProcessBuilder
114 */
115   public Map<String, String> getEnvironment()
116   {
117     return _processBuilder.environment();
118   }
119 
120   /**
121 * @see ProcessBuilder
122 */
123   public File getWorkingDirectory()
124   {
125     return _processBuilder.directory();
126   }
127 
128   /**
129 * @see ProcessBuilder
130 */
131   public void setWorkingDirectory(File directory)
132   {
133     _processBuilder.directory(directory);
134   }
135 
136   /**
137 * @see ProcessBuilder
138 */
139   public boolean getRedirectErrorStream()
140   {
141     return _processBuilder.redirectErrorStream();
142   }
143 
144   /**
145 * @see ProcessBuilder
146 */
147   public void setRedirectErrorStream(boolean redirectErrorStream)
148   {
149     _processBuilder.redirectErrorStream(redirectErrorStream);
150   }
151 
152   public byte[] getOutput() throws InterruptedException
153   {
154     waitFor();
155     return _out.getOutput();
156   }
157 
158   public byte[] getError() throws InterruptedException
159   {
160     waitFor();
161     return _err.getOutput();
162   }
163 
164   /**
165 * Returns the output as a string.
166 *
167 * @param encoding
168 * @return encoded string
169 * @throws InterruptedException
170 * @throws UnsupportedEncodingException
171 */
172   public String getStringOutput(String encoding) throws InterruptedException,
173                                                         UnsupportedEncodingException
174   {
175     return new String(getOutput(), encoding);
176   }
177 
178   /**
179 * Returns the output as a string. Uses encoding "UTF-8".
180 *
181 * @return utf8 encoded string
182 * @throws InterruptedException
183 */
184   public String getStringOutput() throws InterruptedException
185   {
186     try
187     {
188       return getStringOutput("UTF-8");
189     }
190     catch(UnsupportedEncodingException e)
191     {
192       // should not happen
193       throw new RuntimeException(e);
194     }
195   }
196 
197   /**
198 * Returns the error as a string.
199 *
200 * @param encoding
201 * @return error as string
202 * @throws InterruptedException
203 * @throws UnsupportedEncodingException
204 */
205   public String getStringError(String encoding) throws InterruptedException,
206                                                        UnsupportedEncodingException
207   {
208     return new String(getError(), encoding);
209   }
210 
211   /**
212 * Returns the error as a string. Uses encoding "UTF-8".
213 *
214 * @return error as string
215 * @throws InterruptedException
216 */
217   public String getStringError() throws InterruptedException
218   {
219     try
220     {
221       return getStringError("UTF-8");
222     }
223     catch(UnsupportedEncodingException e)
224     {
225       // should not happen
226       throw new RuntimeException(e);
227     }
228   }
229 
230   /**
231 * Properly waits until everything is complete: joins on the thread that
232 * reads the output, joins on the thread that reads the error and finally
233 * wait for the process to be finished.
234 * @return the status code of the process.
235 *
236 * @throws InterruptedException
237 */
238   public int waitFor() throws InterruptedException
239   {
240     if(_process == null)
241       throw new IllegalStateException("you must call start first");
242 
243     _out.join();
244     _err.join();
245     return _process.waitFor();
246   }
247 
248   /**
249 * Properly waits until everything is complete: joins on the thread that
250 * reads the output, joins on the thread that reads the error and finally
251 * wait for the process to be finished.
252 * If the process has not completed before the timeout, throws a
253 * {@link TimeoutException}
254 * @return the status code of the process.
255 *
256 * @throws TimeoutException
257 * @throws InterruptedException
258 */
259   public int waitFor(long timeout) throws InterruptedException, TimeoutException
260   {
261     if(_process == null)
262       throw new IllegalStateException("you must call start first");
263 
264 //    Chronos c = new Chronos();
265     _out.join(timeout);
266 //    timeout -= c.tick();
267     if (timeout <= 0)
268       throw new TimeoutException("Wait timed out");
269     _err.join(timeout);
270 //    timeout -= c.tick();
271     if (timeout <= 0)
272       throw new TimeoutException("Wait timed out");
273 
274     // there is no timeout in this API, not much we can do here
275     // waiting on the other two threads should give us some safety
276     return _process.waitFor();
277   }
278 
279   public int exitValue()
280   {
281     if(_process == null)
282       throw new IllegalStateException("you must call start first");
283 
284     return _process.exitValue();
285   }
286 
287   public void destroy()
288   {
289     if(_process == null)
290       throw new IllegalStateException("you must call start first");
291 
292     _process.destroy();
293   }
294 
295   /**
296 * Creates an external process from the command. It is not started and you have to call
297 * start on it!
298 *
299 * @param commands the command to execute
300 * @return the process */
301   public static ExternalCommand create(String... commands)
302   {
303     ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
304     return ec;
305   }
306 
307   /**
308 * Creates an external process from the command. It is not started and you have to call
309 * start on it!
310 *
311 * @param commands the command to execute
312 * @return the process */
313   public static ExternalCommand create(List<String> commands)
314   {
315     ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
316     return ec;
317   }
318 
319   /**
320 * Creates an external process from the command. The command is executed.
321 *
322 * @param commands the commands to execute
323 * @return the process
324 * @throws IOException if there is an error */
325   public static ExternalCommand start(String... commands) throws IOException
326   {
327     ExternalCommand ec = new ExternalCommand(new ProcessBuilder(commands));
328     ec.start();
329     return ec;
330   }
331 
332   /**
333 * Executes the external command in the given working directory and waits for it to be
334 * finished.
335 *
336 * @param workingDirectory the root directory from where to run the command
337 * @param command the command to execute (should be relative to the working directory
338 * @param args the arguments to the command
339 * @return the process */
340   public static ExternalCommand execute(File workingDirectory,
341                                         String command,
342                                         String... args)
343       throws IOException, InterruptedException
344   {
345     try
346     {
347       return executeWithTimeout(workingDirectory, command, 0, args);
348     }
349     catch (TimeoutException e)
350     {
351       // Can't happen!
352       throw new IllegalStateException(MODULE + ".execute: Unexpected timeout occurred!");
353     }
354   }
355 
356 /**
357 * Executes the external command in the given working directory and waits (until timeout
358 * is elapsed) for it to be finished.
359 *
360 * @param workingDirectory
361 * the root directory from where to run the command
362 * @param command
363 * the command to execute (should be relative to the working directory
364 * @param timeout
365 * the maximum amount of time to wait for this external command (in ms). If
366 * this value is less than or equal to 0, timeout is ignored
367 * @param args
368 * the arguments to the command
369 * @return the process
370 */
371   public static ExternalCommand executeWithTimeout(File workingDirectory,
372                                                    String command,
373                                                    long timeout,
374                                                    String... args)
375       throws IOException, InterruptedException, TimeoutException
376   {
377     List<String> arguments = new ArrayList<String>(args.length + 1);
378 
379     arguments.add(new File(workingDirectory, command).getAbsolutePath());
380     arguments.addAll(Arrays.asList(args));
381 
382     ExternalCommand cmd = ExternalCommand.create(arguments);
383 
384     cmd.setWorkingDirectory(workingDirectory);
385 
386     cmd.setRedirectErrorStream(true);
387 
388     cmd.start();
389 
390     /* Use timeout if it is a valid value! */
391     if (timeout <= 0)
392       cmd.waitFor();
393     else
394       cmd.waitFor(timeout);
395 
396     if (LOG.isDebugEnabled())
397       LOG.debug(cmd.getStringOutput());
398 
399     return cmd;
400   }
401 }