1 package org.apache.maven.surefire.extensions.util;
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 org.apache.maven.surefire.shared.utils.cli.CommandLineException;
23 import org.apache.maven.surefire.shared.utils.cli.Commandline;
24
25 import java.io.Closeable;
26
27 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.addShutDownHook;
28 import static org.apache.maven.surefire.shared.utils.cli.ShutdownHookUtils.removeShutdownHook;
29
30 /**
31 * Programming model with this class:
32 * <pre> {@code
33 * try ( CommandlineExecutor exec = new CommandlineExecutor( cli, endOfStreamsCountdown );
34 * CommandlineStreams streams = exec.execute() )
35 * {
36 * // register exec in the shutdown hook to destroy pending process
37 *
38 * // register streams in the shutdown hook to close all three streams
39 *
40 * ReadableByteChannel stdOut = streams.getStdOutChannel();
41 * ReadableByteChannel stdErr = streams.getStdErrChannel();
42 * WritableByteChannel stdIn = streams.getStdInChannel();
43 * // lineConsumerThread = new LineConsumerThread( ..., stdErr, ..., endOfStreamsCountdown );
44 * // lineConsumerThread.start();
45 *
46 * // stdIn.write( ... );
47 *
48 * int exitCode = exec.awaitExit();
49 * // process exitCode
50 * }
51 * catch ( InterruptedException e )
52 * {
53 * lineConsumerThread.disable();
54 * }
55 * catch ( CommandLineException e )
56 * {
57 * // handle the exceptions
58 * }
59 * } </pre>
60 */
61 public class CommandlineExecutor implements Closeable
62 {
63 private final Commandline cli;
64 private final CountdownCloseable endOfStreamsCountdown;
65 private Process process;
66 private Thread shutdownHook;
67
68 public CommandlineExecutor( Commandline cli, CountdownCloseable endOfStreamsCountdown )
69 {
70 // now the surefire-extension-api is dependent on CLI without casting generic type T to unrelated object
71 // and the user would not use maven-surefire-common nothing but the only surefire-extension-api
72 // because maven-surefire-common is used for MOJO plugin and not the user's extensions. The user does not need
73 // to see all MOJO impl. Only the surefire-api, surefire-logger-api and surefire-extension-api.
74 this.cli = cli;
75 this.endOfStreamsCountdown = endOfStreamsCountdown;
76 }
77
78 public CommandlineStreams execute() throws CommandLineException
79 {
80 process = cli.execute();
81 shutdownHook = new ProcessHook( process );
82 addShutDownHook( shutdownHook );
83 return new CommandlineStreams( process );
84 }
85
86 public int awaitExit() throws InterruptedException
87 {
88 try
89 {
90 return process.waitFor();
91 }
92 finally
93 {
94 endOfStreamsCountdown.awaitClosed();
95 }
96 }
97
98 @Override
99 public void close()
100 {
101 if ( shutdownHook != null )
102 {
103 shutdownHook.run();
104 removeShutdownHook( shutdownHook );
105 shutdownHook = null;
106 }
107 }
108
109 private static class ProcessHook extends Thread
110 {
111 private final Process process;
112
113 private ProcessHook( Process process )
114 {
115 super( "cli-shutdown-hook" );
116 this.process = process;
117 setContextClassLoader( null );
118 setDaemon( true );
119 }
120
121 /** {@inheritDoc} */
122 public void run()
123 {
124 process.destroy();
125 }
126 }
127
128 }