001 /*
002 * Sonar, open source software quality management tool.
003 * Copyright (C) 2008-2011 SonarSource
004 * mailto:contact AT sonarsource DOT com
005 *
006 * Sonar 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 * Sonar 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
017 * License along with Sonar; if not, write to the Free Software
018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
019 */
020 package org.sonar.api.utils.command;
021
022 import java.io.BufferedReader;
023 import java.io.IOException;
024 import java.io.InputStream;
025 import java.io.InputStreamReader;
026 import java.util.concurrent.*;
027
028 import org.apache.commons.io.IOUtils;
029 import org.slf4j.Logger;
030 import org.slf4j.LoggerFactory;
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 get process output, to run asynchronously or to automatically quote
035 * command-line arguments.
036 *
037 * @since 2.7
038 */
039 public final class CommandExecutor {
040
041 private static final CommandExecutor INSTANCE = new CommandExecutor();
042
043 private CommandExecutor() {
044 }
045
046 public static CommandExecutor create() {
047 // stateless object, so a single singleton can be shared
048 return INSTANCE;
049 }
050
051 public int execute(Command command, long timeoutMilliseconds) {
052 ExecutorService executorService = null;
053 Process process = null;
054 StreamGobbler outputGobbler = null;
055 StreamGobbler errorGobbler = null;
056 try {
057 LoggerFactory.getLogger(getClass()).debug("Executing command: " + command);
058 ProcessBuilder builder = new ProcessBuilder(command.toStrings());
059 if (command.getDirectory() != null) {
060 builder.directory(command.getDirectory());
061 }
062 process = builder.start();
063
064 // consume and display the error and output streams
065 outputGobbler = new StreamGobbler(process.getInputStream());
066 errorGobbler = new StreamGobbler(process.getErrorStream());
067 outputGobbler.start();
068 errorGobbler.start();
069
070 final Process finalProcess = process;
071 Callable<Integer> call = new Callable<Integer>() {
072 public Integer call() throws Exception {
073 return finalProcess.waitFor();
074 }
075 };
076
077 executorService = Executors.newSingleThreadExecutor();
078 Future<Integer> ft = executorService.submit(call);
079 return ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
080
081 } catch (TimeoutException te) {
082 process.destroy();
083 throw new CommandException(command, "Timeout exceeded: " + timeoutMilliseconds + " ms", te);
084
085 } catch (Exception e) {
086 throw new CommandException(command, e);
087
088 } finally {
089 waitUntilFinish(outputGobbler);
090 waitUntilFinish(errorGobbler);
091 closeStreams(process);
092
093 if (executorService != null) {
094 executorService.shutdown();
095 }
096 }
097 }
098
099 private void closeStreams(Process process) {
100 if (process != null) {
101 IOUtils.closeQuietly(process.getInputStream());
102 IOUtils.closeQuietly(process.getOutputStream());
103 IOUtils.closeQuietly(process.getErrorStream());
104 }
105 }
106
107 private void waitUntilFinish(StreamGobbler thread) {
108 if (thread != null) {
109 try {
110 thread.join();
111 } catch (InterruptedException e) {
112 // ignore
113 }
114 }
115 }
116
117 private static class StreamGobbler extends Thread {
118 InputStream is;
119
120 StreamGobbler(InputStream is) {
121 super("ProcessStreamGobbler");
122 this.is = is;
123 }
124
125 @Override
126 public void run() {
127 Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
128 InputStreamReader isr = new InputStreamReader(is);
129 BufferedReader br = new BufferedReader(isr);
130 try {
131 String line;
132 while ((line = br.readLine()) != null) {
133 logger.info(line);
134 }
135 } catch (IOException ioe) {
136 logger.error("Error while reading stream", ioe);
137
138 } finally {
139 IOUtils.closeQuietly(br);
140 IOUtils.closeQuietly(isr);
141 }
142 }
143 }
144 }