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 org.apache.commons.io.IOUtils;
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 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 try {
055 LoggerFactory.getLogger(getClass()).debug("Executing command: " + command);
056 ProcessBuilder builder = new ProcessBuilder(command.toStrings());
057 process = builder.start();
058
059 // consume and display the error and output streams
060 StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
061 StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream());
062 outputGobbler.start();
063 errorGobbler.start();
064
065 final Process finalProcess = process;
066 Callable<Integer> call = new Callable<Integer>() {
067 public Integer call() throws Exception {
068 finalProcess.waitFor();
069 return finalProcess.exitValue();
070 }
071 };
072
073 executorService = Executors.newSingleThreadExecutor();
074 Future<Integer> ft = executorService.submit(call);
075 return ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
076
077 } catch (TimeoutException te) {
078 if (process != null) {
079 process.destroy();
080 }
081 throw new CommandException(command, "Timeout exceeded: " + timeoutMilliseconds + " ms", te);
082
083 } catch (Exception e) {
084 throw new CommandException(command, e);
085
086 } finally {
087 if (executorService != null) {
088 executorService.shutdown();
089 }
090 }
091 }
092
093 private static class StreamGobbler extends Thread {
094 InputStream is;
095
096 StreamGobbler(InputStream is) {
097 this.is = is;
098 }
099
100 public void run() {
101 Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
102 InputStreamReader isr = new InputStreamReader(is);
103 BufferedReader br = new BufferedReader(isr);
104 try {
105 String line;
106 while ((line = br.readLine()) != null) {
107 logger.info(line);
108 }
109 } catch (IOException ioe) {
110 logger.error("Error while reading Obeo analyzer output", ioe);
111
112 } finally {
113 IOUtils.closeQuietly(br);
114 IOUtils.closeQuietly(isr);
115 }
116 }
117 }
118 }