View Javadoc
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 }