1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.exec;
18
19 import java.io.Closeable;
20 import java.io.File;
21 import java.io.IOException;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.Map;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.ThreadFactory;
28 import java.util.function.Supplier;
29
30 import org.apache.commons.exec.launcher.CommandLauncher;
31 import org.apache.commons.exec.launcher.CommandLauncherFactory;
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52 public class DefaultExecutor implements Executor {
53
54
55
56
57
58
59
60 public static class Builder<T extends Builder<T>> implements Supplier<DefaultExecutor> {
61
62 private ThreadFactory threadFactory;
63 private ExecuteStreamHandler executeStreamHandler;
64 private Path workingDirectory;
65
66
67
68
69 public Builder() {
70
71 }
72
73
74
75
76
77
78
79
80
81
82
83
84 @SuppressWarnings("unchecked")
85 T asThis() {
86 return (T) this;
87 }
88
89
90
91
92
93
94 @Override
95 public DefaultExecutor get() {
96 return new DefaultExecutor(threadFactory, executeStreamHandler, workingDirectory);
97 }
98
99 ExecuteStreamHandler getExecuteStreamHandler() {
100 return executeStreamHandler;
101 }
102
103 ThreadFactory getThreadFactory() {
104 return threadFactory;
105 }
106
107 Path getWorkingDirectoryPath() {
108 return workingDirectory;
109 }
110
111
112
113
114
115
116
117 public T setExecuteStreamHandler(final ExecuteStreamHandler executeStreamHandler) {
118 this.executeStreamHandler = executeStreamHandler;
119 return asThis();
120 }
121
122
123
124
125
126
127
128 public T setThreadFactory(final ThreadFactory threadFactory) {
129 this.threadFactory = threadFactory;
130 return asThis();
131 }
132
133
134
135
136
137
138
139 public T setWorkingDirectory(final File workingDirectory) {
140 this.workingDirectory = workingDirectory != null ? workingDirectory.toPath() : null;
141 return asThis();
142 }
143
144
145
146
147
148
149
150
151 public T setWorkingDirectory(final Path workingDirectory) {
152 this.workingDirectory = workingDirectory;
153 return asThis();
154 }
155
156 }
157
158
159
160
161
162
163
164 public static Builder<?> builder() {
165 return new Builder<>();
166 }
167
168
169 private ExecuteStreamHandler executeStreamHandler;
170
171
172 private Path workingDirectory;
173
174
175 private ExecuteWatchdog watchdog;
176
177
178 private int[] exitValues;
179
180
181 private final CommandLauncher launcher;
182
183
184 private ProcessDestroyer processDestroyer;
185
186
187 private Thread executorThread;
188
189
190 private IOException exceptionCaught;
191
192
193
194
195 private final ThreadFactory threadFactory;
196
197
198
199
200
201
202
203
204
205 @Deprecated
206 public DefaultExecutor() {
207 this(Executors.defaultThreadFactory(), new PumpStreamHandler(), Paths.get("."));
208 }
209
210 DefaultExecutor(final ThreadFactory threadFactory, final ExecuteStreamHandler executeStreamHandler, final Path workingDirectory) {
211 this.threadFactory = threadFactory != null ? threadFactory : Executors.defaultThreadFactory();
212 this.executeStreamHandler = executeStreamHandler != null ? executeStreamHandler : new PumpStreamHandler();
213 this.workingDirectory = workingDirectory != null ? workingDirectory : Paths.get(".");
214 this.launcher = CommandLauncherFactory.createVMLauncher();
215 this.exitValues = new int[0];
216 }
217
218 private void checkWorkingDirectory() throws IOException {
219 checkWorkingDirectory(workingDirectory);
220 }
221
222 private void checkWorkingDirectory(final File directory) throws IOException {
223 if (directory != null && !directory.exists()) {
224 throw new IOException(directory + " doesn't exist.");
225 }
226 }
227
228 private void checkWorkingDirectory(final Path directory) throws IOException {
229 if (directory != null && !Files.exists(directory)) {
230 throw new IOException(directory + " doesn't exist.");
231 }
232 }
233
234
235
236
237
238
239 private void closeCatch(final Closeable closeable) {
240 try {
241 closeable.close();
242 } catch (final IOException e) {
243 setExceptionCaught(e);
244 }
245 }
246
247
248
249
250
251
252 @SuppressWarnings("resource")
253 private void closeProcessStreams(final Process process) {
254 closeCatch(process.getInputStream());
255 closeCatch(process.getOutputStream());
256 closeCatch(process.getErrorStream());
257 }
258
259
260
261
262
263
264
265
266 protected Thread createThread(final Runnable runnable, final String name) {
267 return ThreadUtil.newThread(threadFactory, runnable, name, false);
268 }
269
270
271
272
273 @Override
274 public int execute(final CommandLine command) throws ExecuteException, IOException {
275 return execute(command, (Map<String, String>) null);
276 }
277
278
279
280
281 @Override
282 public void execute(final CommandLine command, final ExecuteResultHandler handler) throws ExecuteException, IOException {
283 execute(command, null, handler);
284 }
285
286
287
288
289 @Override
290 public int execute(final CommandLine command, final Map<String, String> environment) throws ExecuteException, IOException {
291 checkWorkingDirectory();
292 return executeInternal(command, environment, workingDirectory, executeStreamHandler);
293 }
294
295
296
297
298 @Override
299 public void execute(final CommandLine command, final Map<String, String> environment, final ExecuteResultHandler handler)
300 throws ExecuteException, IOException {
301 checkWorkingDirectory();
302 if (watchdog != null) {
303 watchdog.setProcessNotStarted();
304 }
305 executorThread = createThread(() -> {
306 int exitValue = INVALID_EXITVALUE;
307 try {
308 exitValue = executeInternal(command, environment, workingDirectory, executeStreamHandler);
309 handler.onProcessComplete(exitValue);
310 } catch (final ExecuteException e) {
311 handler.onProcessFailed(e);
312 } catch (final Exception e) {
313 handler.onProcessFailed(new ExecuteException("Execution failed", exitValue, e));
314 }
315 }, "CommonsExecDefaultExecutor");
316 getExecutorThread().start();
317 }
318
319
320
321
322
323
324
325
326
327
328
329 private int executeInternal(final CommandLine command, final Map<String, String> environment, final Path workingDirectory,
330 final ExecuteStreamHandler streams) throws IOException {
331 final Process process;
332 exceptionCaught = null;
333 try {
334 process = launch(command, environment, workingDirectory);
335 } catch (final IOException e) {
336 if (watchdog != null) {
337 watchdog.failedToStart(e);
338 }
339 throw e;
340 }
341 try {
342 setStreams(streams, process);
343 } catch (final IOException e) {
344 process.destroy();
345 if (watchdog != null) {
346 watchdog.failedToStart(e);
347 }
348 throw e;
349 }
350 streams.start();
351 try {
352
353 if (getProcessDestroyer() != null) {
354 getProcessDestroyer().add(process);
355 }
356
357 if (watchdog != null) {
358 watchdog.start(process);
359 }
360 int exitValue = INVALID_EXITVALUE;
361 try {
362 exitValue = process.waitFor();
363 } catch (final InterruptedException e) {
364 process.destroy();
365 } finally {
366
367
368
369
370 Thread.interrupted();
371 }
372 if (watchdog != null) {
373 watchdog.stop();
374 }
375 try {
376 streams.stop();
377 } catch (final IOException e) {
378 setExceptionCaught(e);
379 }
380 closeProcessStreams(process);
381 if (getExceptionCaught() != null) {
382 throw getExceptionCaught();
383 }
384 if (watchdog != null) {
385 try {
386 watchdog.checkException();
387 } catch (final IOException e) {
388 throw e;
389 } catch (final Exception e) {
390 throw new IOException(e);
391 }
392 }
393 if (isFailure(exitValue)) {
394 throw new ExecuteException("Process exited with an error: " + exitValue, exitValue);
395 }
396 return exitValue;
397 } finally {
398
399 if (getProcessDestroyer() != null) {
400 getProcessDestroyer().remove(process);
401 }
402 }
403 }
404
405
406
407
408
409
410 private IOException getExceptionCaught() {
411 return exceptionCaught;
412 }
413
414
415
416
417
418
419 protected Thread getExecutorThread() {
420 return executorThread;
421 }
422
423
424
425
426 @Override
427 public ProcessDestroyer getProcessDestroyer() {
428 return processDestroyer;
429 }
430
431
432
433
434 @Override
435 public ExecuteStreamHandler getStreamHandler() {
436 return executeStreamHandler;
437 }
438
439
440
441
442
443
444 ThreadFactory getThreadFactory() {
445 return threadFactory;
446 }
447
448
449
450
451 @Override
452 public ExecuteWatchdog getWatchdog() {
453 return watchdog;
454 }
455
456
457
458
459 @Override
460 public File getWorkingDirectory() {
461 return workingDirectory.toFile();
462 }
463
464
465 @Override
466 public boolean isFailure(final int exitValue) {
467 if (exitValues == null) {
468 return false;
469 }
470 if (exitValues.length == 0) {
471 return launcher.isFailure(exitValue);
472 }
473 for (final int exitValue2 : exitValues) {
474 if (exitValue2 == exitValue) {
475 return false;
476 }
477 }
478 return true;
479 }
480
481
482
483
484
485
486
487
488
489
490 protected Process launch(final CommandLine command, final Map<String, String> env, final File workingDirectory) throws IOException {
491 if (launcher == null) {
492 throw new IllegalStateException("CommandLauncher cannot be null");
493 }
494 checkWorkingDirectory(workingDirectory);
495 return launcher.exec(command, env, workingDirectory);
496 }
497
498
499
500
501
502
503
504
505
506
507
508 protected Process launch(final CommandLine command, final Map<String, String> env, final Path workingDirectory) throws IOException {
509 if (launcher == null) {
510 throw new IllegalStateException("CommandLauncher cannot be null");
511 }
512 checkWorkingDirectory(workingDirectory);
513 return launcher.exec(command, env, workingDirectory);
514 }
515
516
517
518
519
520
521 private void setExceptionCaught(final IOException e) {
522 if (exceptionCaught == null) {
523 exceptionCaught = e;
524 }
525 }
526
527
528 @Override
529 public void setExitValue(final int value) {
530 setExitValues(new int[] { value });
531 }
532
533
534 @Override
535 public void setExitValues(final int[] values) {
536 exitValues = values == null ? null : (int[]) values.clone();
537 }
538
539
540
541
542 @Override
543 public void setProcessDestroyer(final ProcessDestroyer processDestroyer) {
544 this.processDestroyer = processDestroyer;
545 }
546
547
548
549
550 @Override
551 public void setStreamHandler(final ExecuteStreamHandler streamHandler) {
552 this.executeStreamHandler = streamHandler;
553 }
554
555 @SuppressWarnings("resource")
556 private void setStreams(final ExecuteStreamHandler streams, final Process process) throws IOException {
557 streams.setProcessInputStream(process.getOutputStream());
558 streams.setProcessOutputStream(process.getInputStream());
559 streams.setProcessErrorStream(process.getErrorStream());
560 }
561
562
563
564
565 @Override
566 public void setWatchdog(final ExecuteWatchdog watchdog) {
567 this.watchdog = watchdog;
568 }
569
570
571
572
573
574
575
576 @Deprecated
577 @Override
578 public void setWorkingDirectory(final File workingDirectory) {
579 this.workingDirectory = workingDirectory != null ? workingDirectory.toPath() : null;
580 }
581
582 }