001 /* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2014 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020 package org.sonar.api.utils.command; 021 022 import com.google.common.io.Closeables; 023 import org.slf4j.Logger; 024 import org.slf4j.LoggerFactory; 025 026 import java.io.BufferedReader; 027 import java.io.IOException; 028 import java.io.InputStream; 029 import java.io.InputStreamReader; 030 import java.util.concurrent.*; 031 032 /** 033 * Synchronously execute a native command line. It's much more limited than the Apache Commons Exec library. 034 * For example it does not allow to run asynchronously or to automatically quote command-line arguments. 035 * 036 * @since 2.7 037 */ 038 public class CommandExecutor { 039 040 private static final Logger LOG = LoggerFactory.getLogger(CommandExecutor.class); 041 042 private static final CommandExecutor INSTANCE = new CommandExecutor(); 043 044 private CommandExecutor() { 045 } 046 047 public static CommandExecutor create() { 048 // stateless object, so a single singleton can be shared 049 return INSTANCE; 050 } 051 052 /** 053 * @throws org.sonar.api.utils.command.TimeoutException on timeout, since 4.4 054 * @throws CommandException on any other error 055 * @since 3.0 056 */ 057 public int execute(Command command, StreamConsumer stdOut, StreamConsumer stdErr, long timeoutMilliseconds) { 058 ExecutorService executorService = null; 059 Process process = null; 060 StreamGobbler outputGobbler = null; 061 StreamGobbler errorGobbler = null; 062 try { 063 ProcessBuilder builder = new ProcessBuilder(command.toStrings()); 064 if (command.getDirectory() != null) { 065 builder.directory(command.getDirectory()); 066 } 067 builder.environment().putAll(command.getEnvironmentVariables()); 068 process = builder.start(); 069 070 outputGobbler = new StreamGobbler(process.getInputStream(), stdOut); 071 errorGobbler = new StreamGobbler(process.getErrorStream(), stdErr); 072 outputGobbler.start(); 073 errorGobbler.start(); 074 075 final Process finalProcess = process; 076 executorService = Executors.newSingleThreadExecutor(); 077 Future<Integer> ft = executorService.submit(new Callable<Integer>() { 078 @Override 079 public Integer call() throws Exception { 080 return finalProcess.waitFor(); 081 } 082 }); 083 int exitCode = ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS); 084 waitUntilFinish(outputGobbler); 085 waitUntilFinish(errorGobbler); 086 verifyGobbler(command, outputGobbler, "stdOut"); 087 verifyGobbler(command, errorGobbler, "stdErr"); 088 return exitCode; 089 090 } catch (java.util.concurrent.TimeoutException te) { 091 process.destroy(); 092 throw new TimeoutException(command, "Timeout exceeded: " + timeoutMilliseconds + " ms", te); 093 094 } catch (CommandException e) { 095 throw e; 096 097 } catch (Exception e) { 098 throw new CommandException(command, e); 099 100 } finally { 101 waitUntilFinish(outputGobbler); 102 waitUntilFinish(errorGobbler); 103 closeStreams(process); 104 105 if (executorService != null) { 106 executorService.shutdown(); 107 } 108 } 109 } 110 111 private void verifyGobbler(Command command, StreamGobbler gobbler, String type) { 112 if (gobbler.getException() != null) { 113 throw new CommandException(command, "Error inside " + type + " stream", gobbler.getException()); 114 } 115 } 116 117 /** 118 * Execute command and display error and output streams in log. 119 * Method {@link #execute(Command, StreamConsumer, StreamConsumer, long)} is preferable, 120 * when fine-grained control of output of command required. 121 * 122 * @throws CommandException 123 */ 124 public int execute(Command command, long timeoutMilliseconds) { 125 LOG.info("Executing command: " + command); 126 return execute(command, new DefaultConsumer(), new DefaultConsumer(), timeoutMilliseconds); 127 } 128 129 private void closeStreams(Process process) { 130 if (process != null) { 131 Closeables.closeQuietly(process.getInputStream()); 132 Closeables.closeQuietly(process.getInputStream()); 133 Closeables.closeQuietly(process.getOutputStream()); 134 Closeables.closeQuietly(process.getErrorStream()); 135 } 136 } 137 138 private void waitUntilFinish(StreamGobbler thread) { 139 if (thread != null) { 140 try { 141 thread.join(); 142 } catch (InterruptedException e) { 143 LOG.error("InterruptedException while waiting finish of " + thread.toString(), e); 144 } 145 } 146 } 147 148 private static class StreamGobbler extends Thread { 149 private final InputStream is; 150 private final StreamConsumer consumer; 151 private volatile Exception exception; 152 153 StreamGobbler(InputStream is, StreamConsumer consumer) { 154 super("ProcessStreamGobbler"); 155 this.is = is; 156 this.consumer = consumer; 157 } 158 159 @Override 160 public void run() { 161 InputStreamReader isr = new InputStreamReader(is); 162 BufferedReader br = new BufferedReader(isr); 163 try { 164 String line; 165 while ((line = br.readLine()) != null) { 166 consumeLine(line); 167 } 168 } catch (IOException ioe) { 169 exception = ioe; 170 171 } finally { 172 Closeables.closeQuietly(br); 173 Closeables.closeQuietly(isr); 174 } 175 } 176 177 private void consumeLine(String line) { 178 if (exception == null) { 179 try { 180 consumer.consumeLine(line); 181 } catch (Exception e) { 182 exception = e; 183 } 184 } 185 } 186 187 public Exception getException() { 188 return exception; 189 } 190 } 191 192 private static class DefaultConsumer implements StreamConsumer { 193 public void consumeLine(String line) { 194 LOG.info(line); 195 } 196 } 197 }