1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. 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,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package org.apache.maven.surefire.extensions.util;
20
21 import java.io.Closeable;
22
23 import org.apache.maven.surefire.shared.utils.cli.CommandLineException;
24 import org.apache.maven.surefire.shared.utils.cli.Commandline;
25
26 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
27 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
28
29 /**
30 * Programming model with this class:
31 * <pre> {@code
32 * try ( CommandlineExecutor exec = new CommandlineExecutor( cli, endOfStreamsCountdown );
33 * CommandlineStreams streams = exec.execute() )
34 * {
35 * // register exec in the shutdown hook to destroy pending process
36 *
37 * // register streams in the shutdown hook to close all three streams
38 *
39 * ReadableByteChannel stdOut = streams.getStdOutChannel();
40 * ReadableByteChannel stdErr = streams.getStdErrChannel();
41 * WritableByteChannel stdIn = streams.getStdInChannel();
42 * // lineConsumerThread = new LineConsumerThread( ..., stdErr, ..., endOfStreamsCountdown );
43 * // lineConsumerThread.start();
44 *
45 * // stdIn.write( ... );
46 *
47 * int exitCode = exec.awaitExit();
48 * // process exitCode
49 * }
50 * catch ( InterruptedException e )
51 * {
52 * lineConsumerThread.disable();
53 * }
54 * catch ( CommandLineException e )
55 * {
56 * // handle the exceptions
57 * }
58 * } </pre>
59 */
60 public class CommandlineExecutor implements Closeable {
61 private final Commandline cli;
62 private final CountdownCloseable endOfStreamsCountdown;
63 private Process process;
64 private Thread shutdownHook;
65
66 public CommandlineExecutor(Commandline cli, CountdownCloseable endOfStreamsCountdown) {
67 // now the surefire-extension-api is dependent on CLI without casting generic type T to unrelated object
68 // and the user would not use maven-surefire-common nothing but the only surefire-extension-api
69 // because maven-surefire-common is used for MOJO plugin and not the user's extensions. The user does not need
70 // to see all MOJO impl. Only the surefire-api, surefire-logger-api and surefire-extension-api.
71 this.cli = cli;
72 this.endOfStreamsCountdown = endOfStreamsCountdown;
73 }
74
75 public CommandlineStreams execute() throws CommandLineException {
76 process = cli.execute();
77 shutdownHook = new ProcessHook(process);
78 addShutDownHook(shutdownHook);
79 return new CommandlineStreams(process);
80 }
81
82 public int awaitExit() throws InterruptedException {
83 try {
84 return process.waitFor();
85 } finally {
86 endOfStreamsCountdown.awaitClosed();
87 }
88 }
89
90 @Override
91 public void close() {
92 if (shutdownHook != null) {
93 shutdownHook.run();
94 removeShutdownHook(shutdownHook);
95 shutdownHook = null;
96 }
97 }
98
99 private static class ProcessHook extends Thread {
100 private final Process process;
101
102 private ProcessHook(Process process) {
103 super("cli-shutdown-hook");
104 this.process = process;
105 setContextClassLoader(null);
106 setDaemon(true);
107 }
108
109 /** {@inheritDoc} */
110 public void run() {
111 process.destroy();
112 }
113 }
114 }