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