--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.utils.command;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * @since 2.7
+ */
+public final class Command {
+
+ private String executable;
+ private List<String> arguments = Lists.newArrayList();
+
+ private Command(String executable) {
+ this.executable = executable;
+ }
+
+ public String getExecutable() {
+ return executable;
+ }
+
+ public List<String> getArguments() {
+ return Collections.unmodifiableList(arguments);
+ }
+
+ public Command addArgument(String arg) {
+ arguments.add(arg);
+ return this;
+ }
+
+ public Command addArguments(List<String> args) {
+ arguments.addAll(args);
+ return this;
+ }
+
+ public Command addArguments(String[] args) {
+ arguments.addAll(Arrays.asList(args));
+ return this;
+ }
+
+ String[] toStrings() {
+ List<String> command = Lists.newArrayList();
+ command.add(executable);
+ command.addAll(arguments);
+ return command.toArray(new String[command.size()]);
+ }
+
+ public String toCommandLine() {
+ return Joiner.on(" ").join(toStrings());
+ }
+
+ @Override
+ public String toString() {
+ return toCommandLine();
+ }
+
+ /**
+ * Create a command line without any arguments
+ * @param executable
+ */
+ public static Command create(String executable) {
+ if (StringUtils.isBlank(executable)) {
+ throw new IllegalArgumentException("Command executable can not be blank");
+ }
+ return new Command(executable);
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.utils.command;
+
+public final class CommandException extends RuntimeException {
+ private Command command;
+
+ public CommandException(Command command, String message, Throwable throwable) {
+ super(message + " [command: " + command + "]", throwable);
+ this.command = command;
+ }
+
+ public CommandException(Command command, Throwable throwable) {
+ super(throwable);
+ this.command = command;
+ }
+
+ public Command getCommand() {
+ return command;
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.utils.command;
+
+import org.apache.commons.io.IOUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.concurrent.*;
+
+/**
+ * Synchronously execute a native command line. It's much more limited than the Apache Commons Exec library.
+ * For example it does not allow to get process output, to run asynchronously or to automatically quote
+ * command-line arguments.
+ *
+ * @since 2.7
+ */
+public final class CommandExecutor {
+
+ private static final CommandExecutor INSTANCE = new CommandExecutor();
+
+ private CommandExecutor() {
+ }
+
+ public static CommandExecutor create() {
+ // stateless object, so a single singleton can be shared
+ return INSTANCE;
+ }
+
+ public int execute(Command command, long timeoutMilliseconds) {
+ ExecutorService executorService = null;
+ Process process = null;
+ try {
+ LoggerFactory.getLogger(getClass()).debug("Executing command: " + command);
+ ProcessBuilder builder = new ProcessBuilder(command.toStrings());
+ process = builder.start();
+
+ // consume and display the error and output streams
+ StreamGobbler outputGobbler = new StreamGobbler(process.getInputStream());
+ StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream());
+ outputGobbler.start();
+ errorGobbler.start();
+
+ final Process finalProcess = process;
+ Callable<Integer> call = new Callable<Integer>() {
+ public Integer call() throws Exception {
+ finalProcess.waitFor();
+ return finalProcess.exitValue();
+ }
+ };
+
+ executorService = Executors.newSingleThreadExecutor();
+ Future<Integer> ft = executorService.submit(call);
+ return ft.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
+
+ } catch (TimeoutException te) {
+ if (process != null) {
+ process.destroy();
+ }
+ throw new CommandException(command, "Timeout exceeded: " + timeoutMilliseconds + " ms", te);
+
+ } catch (Exception e) {
+ throw new CommandException(command, e);
+
+ } finally {
+ if (executorService != null) {
+ executorService.shutdown();
+ }
+ }
+ }
+
+ private static class StreamGobbler extends Thread {
+ InputStream is;
+
+ StreamGobbler(InputStream is) {
+ this.is = is;
+ }
+
+ public void run() {
+ Logger logger = LoggerFactory.getLogger(CommandExecutor.class);
+ InputStreamReader isr = new InputStreamReader(is);
+ BufferedReader br = new BufferedReader(isr);
+ try {
+ String line;
+ while ((line = br.readLine()) != null) {
+ logger.info(line);
+ }
+ } catch (IOException ioe) {
+ logger.error("Error while reading Obeo analyzer output", ioe);
+
+ } finally {
+ IOUtils.closeQuietly(br);
+ IOUtils.closeQuietly(isr);
+ }
+ }
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.utils.command;
+
+import org.apache.commons.lang.SystemUtils;
+import org.junit.Test;
+
+import java.io.File;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.number.OrderingComparisons.greaterThanOrEqualTo;
+import static org.hamcrest.number.OrderingComparisons.lessThan;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.fail;
+
+public class CommandExecutorTest {
+
+ @Test
+ public void shouldEchoArguments() {
+ String executable = getScript("echo");
+ int exitCode = CommandExecutor.create().execute(Command.create(executable), 1000L);
+ assertThat(exitCode, is(0));
+ }
+
+ @Test
+ public void shouldStopWithTimeout() {
+ String executable = getScript("forever");
+ long start = System.currentTimeMillis();
+ try {
+ CommandExecutor.create().execute(Command.create(executable), 100L);
+ fail();
+ } catch (CommandException e) {
+ long duration = System.currentTimeMillis()-start;
+ assertThat(e.getMessage(), duration, greaterThanOrEqualTo(100L));
+ assertThat(e.getMessage(), duration, lessThan(1000L));
+ }
+ }
+
+ @Test(expected = CommandException.class)
+ public void shouldFailIfScriptNotFound() {
+ CommandExecutor.create().execute(Command.create("notfound"), 1000L);
+ }
+
+ private String getScript(String name) {
+ String filename;
+ if (SystemUtils.IS_OS_WINDOWS) {
+ filename = name + ".bat";
+ } else {
+ filename = name + ".sh";
+ }
+ return new File("src/test/scripts/" + filename).getPath();
+ }
+}
--- /dev/null
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.api.utils.command;
+
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertThat;
+
+public class CommandTest {
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailWhenBlankExecutable() throws Exception {
+ Command.create(" ");
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void shouldFailWhenNullExecutable() throws Exception {
+ Command.create(null);
+ }
+
+ @Test
+ public void shouldCreateCommand() throws Exception {
+ Command command = Command.create("java");
+ command.addArgument("-Xmx512m");
+ command.addArgument("-Dfoo=bar");
+ assertThat(command.getExecutable(), is("java"));
+ assertThat(command.getArguments().size(), is(2));
+ assertThat(command.toCommandLine(), is("java -Xmx512m -Dfoo=bar"));
+ }
+}
--- /dev/null
+@ECHO OFF
+@ECHO "Parameter: " + %1
--- /dev/null
+#!/bin/sh
+echo "Parameter: " + $1
\ No newline at end of file
--- /dev/null
+@ECHO OFF
+
+:LOOP
+ @ping 127.0.0.1 -n 2 -w 1000 > nul
+GOTO LOOP
--- /dev/null
+#!/bin/sh
+while test "notempty"
+do
+ sleep 1
+done
\ No newline at end of file