aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-runner-api/src/main/java/org/sonar
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-runner-api/src/main/java/org/sonar')
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/Command.java126
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java198
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java14
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java206
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java243
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/LogOutput.java (renamed from sonar-runner-api/src/main/java/org/sonar/runner/api/LogListener.java)5
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java181
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java47
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/api/StdOutLogOutput.java (renamed from sonar-runner-api/src/main/java/org/sonar/runner/api/CommandException.java)10
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/InternalProperties.java26
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedClassloader.java111
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java94
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java85
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java (renamed from sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java)24
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/JarExtractor.java (renamed from sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java)32
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java99
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/RunnerException.java28
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java165
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java65
-rw-r--r--sonar-runner-api/src/main/java/org/sonar/runner/impl/package-info.java (renamed from sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java)9
20 files changed, 866 insertions, 902 deletions
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Command.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Command.java
deleted file mode 100644
index b427c14..0000000
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Command.java
+++ /dev/null
@@ -1,126 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * dev@sonar.codehaus.org
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.runner.api;
-
-import java.io.File;
-import java.util.*;
-
-class Command {
- private final String executable;
- private final List<String> arguments;
- private final Map<String, String> env;
- private final File directory;
-
- private Command(Builder builder) {
- this.executable = builder.executable;
- this.arguments = Collections.unmodifiableList(builder.arguments);
- this.env = Collections.unmodifiableMap(builder.env);
- this.directory = builder.directory;
- }
-
- File directory() {
- return directory;
- }
-
- String executable() {
- return executable;
- }
-
- List<String> arguments() {
- return arguments;
- }
-
- /**
- * Environment variables that are propagated during command execution.
- *
- * @return a non-null and immutable map of variables
- */
- Map<String, String> envVariables() {
- return env;
- }
-
- String[] toStrings() {
- String[] strings = new String[1 + arguments.size()];
- strings[0] = executable;
- for (int index = 0; index < arguments.size(); index++) {
- strings[index + 1] = arguments.get(index);
- }
- return strings;
- }
-
- @Override
- public String toString() {
- return Utils.join(toStrings(), " ");
- }
-
- static Builder builder() {
- return new Builder();
- }
-
- static class Builder {
- private String executable;
- private final List<String> arguments = new ArrayList<String>();
- private final Map<String, String> env = new HashMap<String, String>();
- private File directory;
-
- private Builder() {
- }
-
- Builder setExecutable(String s) {
- this.executable = s;
- return this;
- }
-
- Builder addArguments(String... args) {
- return addArguments(Arrays.asList(args));
- }
-
- Builder addArguments(List<String> args) {
- for (String arg : args) {
- if (arg != null && !"".equals(arg.trim())) {
- arguments.add(arg);
- }
- }
- return this;
- }
-
- Builder setEnvVariable(String key, String value) {
- env.put(key, value);
- return this;
- }
-
- Builder addEnvVariables(Map<String, String> map) {
- env.putAll(map);
- return this;
- }
-
- Builder setDirectory(File d) {
- this.directory = d;
- return this;
- }
-
- Command build() {
- if (executable == null) {
- throw new IllegalArgumentException("Command executable is not defined");
- }
- return new Command(this);
- }
- }
-}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java
deleted file mode 100644
index 27140b3..0000000
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandExecutor.java
+++ /dev/null
@@ -1,198 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * dev@sonar.codehaus.org
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.runner.api;
-
-import org.sonar.runner.impl.Logs;
-
-import javax.annotation.Nullable;
-
-import java.io.BufferedReader;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.util.concurrent.Callable;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.Future;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.TimeoutException;
-
-/**
- * Synchronously execute a native command line. It's much more limited than the Apache Commons Exec library.
- * For example it does not allow to run asynchronously or to automatically quote command-line arguments.
- *
- * @since 2.7
- */
-class CommandExecutor {
-
- private static final CommandExecutor INSTANCE = new CommandExecutor();
-
- private CommandExecutor() {
- }
-
- static CommandExecutor create() {
- // stateless object, so a single singleton can be shared
- return INSTANCE;
- }
-
- int execute(Command command, StreamConsumer stdOut, StreamConsumer stdErr, long timeoutMilliseconds, @Nullable ProcessMonitor processMonitor) {
- ExecutorService executorService = null;
- Process process = null;
- StreamGobbler outputGobbler = null;
- StreamGobbler errorGobbler = null;
- try {
- ProcessBuilder builder = new ProcessBuilder(command.toStrings());
- builder.directory(command.directory());
- builder.environment().putAll(command.envVariables());
- process = builder.start();
-
- outputGobbler = new StreamGobbler(process.getInputStream(), stdOut);
- errorGobbler = new StreamGobbler(process.getErrorStream(), stdErr);
- outputGobbler.start();
- errorGobbler.start();
-
- executorService = Executors.newSingleThreadExecutor();
- final Future<Integer> futureTask = executeProcess(executorService, process);
- if (processMonitor != null) {
- monitorProcess(processMonitor, executorService, process);
- }
-
- int exitCode = futureTask.get(timeoutMilliseconds, TimeUnit.MILLISECONDS);
- waitUntilFinish(outputGobbler);
- waitUntilFinish(errorGobbler);
- verifyGobbler(command, outputGobbler, "stdOut");
- verifyGobbler(command, errorGobbler, "stdErr");
- return exitCode;
-
- } catch (TimeoutException te) {
- if (process != null) {
- process.destroy();
- }
- throw new CommandException("Timeout exceeded: " + timeoutMilliseconds + " ms", command, te);
-
- } catch (CommandException e) {
- throw e;
-
- } catch (Exception e) {
- throw new CommandException("Fail to execute command", command, e);
-
- } finally {
- waitUntilFinish(outputGobbler);
- waitUntilFinish(errorGobbler);
- closeStreams(process);
- if (executorService != null) {
- executorService.shutdown();
- }
- }
- }
-
- private static void monitorProcess(final ProcessMonitor processMonitor, final ExecutorService executor, final Process process) {
- new Thread() {
- @Override
- public void run() {
- while (!executor.isTerminated()) {
- if (processMonitor.stop()) {
- process.destroy();
- }
- try {
- Thread.sleep(100);
- } catch (InterruptedException e) {
- // ignore
- }
- }
- }
- }.start();
- }
-
- private Future<Integer> executeProcess(ExecutorService executorService, Process process) {
- final Process finalProcess = process;
- return executorService.submit(new Callable<Integer>() {
- @Override
- public Integer call() throws InterruptedException {
- return finalProcess.waitFor();
- }
- });
- }
-
- private static void verifyGobbler(Command command, StreamGobbler gobbler, String type) {
- if (gobbler.getException() != null) {
- throw new CommandException("Error inside " + type + " stream", command, gobbler.getException());
- }
- }
-
- private static void closeStreams(Process process) {
- if (process != null) {
- Utils.closeQuietly(process.getInputStream());
- Utils.closeQuietly(process.getInputStream());
- Utils.closeQuietly(process.getOutputStream());
- Utils.closeQuietly(process.getErrorStream());
- }
- }
-
- private static void waitUntilFinish(StreamGobbler thread) {
- if (thread != null) {
- try {
- thread.join();
- } catch (InterruptedException e) {
- Logs.error("InterruptedException while waiting finish of " + thread.toString(), e);
- }
- }
- }
-
- private static class StreamGobbler extends Thread {
- private final InputStream is;
- private final StreamConsumer consumer;
- private volatile Exception exception;
-
- StreamGobbler(InputStream is, StreamConsumer consumer) {
- super("ProcessStreamGobbler");
- this.is = is;
- this.consumer = consumer;
- }
-
- @Override
- public void run() {
- try (InputStreamReader isr = new InputStreamReader(is);
- BufferedReader br = new BufferedReader(isr)) {
- String line;
- while ((line = br.readLine()) != null) {
- consumeLine(line);
- }
- } catch (IOException ioe) {
- exception = ioe;
-
- }
- }
-
- private void consumeLine(String line) {
- if (exception == null) {
- try {
- consumer.consumeLine(line);
- } catch (Exception e) {
- exception = e;
- }
- }
- }
-
- public Exception getException() {
- return exception;
- }
- }
-}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java
index 2ab4d6c..c592237 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/Dirs.java
@@ -19,17 +19,19 @@
*/
package org.sonar.runner.api;
-import org.sonar.runner.impl.Logs;
-
import java.io.File;
import java.util.Properties;
+import org.sonar.home.cache.Logger;
class Dirs {
- private Dirs() {
+ private final Logger logger;
+
+ Dirs(Logger logger) {
+ this.logger = logger;
}
- static void init(Properties p) {
+ void init(Properties p) {
boolean onProject = Utils.taskRequiresProject(p);
if (onProject) {
initProjectDirs(p);
@@ -38,7 +40,7 @@ class Dirs {
}
}
- private static void initProjectDirs(Properties p) {
+ private void initProjectDirs(Properties p) {
String path = p.getProperty(ScanProperties.PROJECT_BASEDIR, ".");
File projectDir = new File(path);
if (!projectDir.isDirectory()) {
@@ -59,7 +61,7 @@ class Dirs {
}
Utils.deleteQuietly(workDir);
p.setProperty(RunnerProperties.WORK_DIR, workDir.getAbsolutePath());
- Logs.info("Work directory: " + workDir.getAbsolutePath());
+ logger.info("Work directory: " + workDir.getAbsolutePath());
}
/**
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
index 4883ea0..d48ca77 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
@@ -19,110 +19,202 @@
*/
package org.sonar.runner.api;
-import org.sonar.home.log.LogListener;
-
-import org.sonar.runner.impl.Logs;
+import java.io.File;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.nio.charset.Charset;
+import java.util.Locale;
+import java.util.Properties;
+import javax.annotation.Nullable;
+import org.sonar.home.cache.Logger;
import org.sonar.runner.batch.IsolatedLauncher;
+import org.sonar.runner.impl.InternalProperties;
import org.sonar.runner.impl.IsolatedLauncherFactory;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
-import java.util.Properties;
-
/**
- * Implementation of {@link Runner} that is executed in the same JVM. The application can inject
- * some extensions into Sonar IoC container (see {@link #addExtensions(Object...)}. It can be
- * used for example in the Maven Sonar plugin to register Maven components like MavenProject
- * or MavenPluginExecutor.
+ * Entry point to run SonarQube analysis programmatically.
* @since 2.2
*/
-public class EmbeddedRunner extends Runner<EmbeddedRunner> {
+public class EmbeddedRunner {
private final IsolatedLauncherFactory launcherFactory;
private IsolatedLauncher launcher;
- private final List<Object> extensions = new ArrayList<Object>();
- private static final String MASK_RULES_PROP = "sonarRunner.maskRules";
+ private final LogOutput logOutput;
+ private final Properties globalProperties = new Properties();
+ private final Logger logger;
- EmbeddedRunner(IsolatedLauncherFactory bl) {
+ EmbeddedRunner(IsolatedLauncherFactory bl, Logger logger, LogOutput logOutput) {
+ this.logger = logger;
this.launcherFactory = bl;
+ this.logOutput = logOutput;
}
- /**
- * Create a new instance.
- */
- public static EmbeddedRunner create() {
- return new EmbeddedRunner(new IsolatedLauncherFactory());
+ public static EmbeddedRunner create(final LogOutput logOutput) {
+ Logger logger = new Logger() {
+
+ @Override
+ public void warn(String msg) {
+ logOutput.log(msg, LogOutput.Level.WARN);
+ }
+
+ @Override
+ public void info(String msg) {
+ logOutput.log(msg, LogOutput.Level.INFO);
+ }
+
+ @Override
+ public void error(String msg, Throwable t) {
+ StringWriter errors = new StringWriter();
+ t.printStackTrace(new PrintWriter(errors));
+ logOutput.log(msg + "\n" + errors.toString(), LogOutput.Level.ERROR);
+ }
+
+ @Override
+ public void error(String msg) {
+ logOutput.log(msg, LogOutput.Level.ERROR);
+ }
+
+ @Override
+ public void debug(String msg) {
+ logOutput.log(msg, LogOutput.Level.DEBUG);
+ }
+ };
+ return new EmbeddedRunner(new IsolatedLauncherFactory(logger), logger, logOutput);
+ }
+
+ public Properties globalProperties() {
+ Properties clone = new Properties();
+ clone.putAll(globalProperties);
+ return clone;
}
- public static EmbeddedRunner create(LogListener logListener) {
- Logs.setListener(logListener);
- return new EmbeddedRunner(new IsolatedLauncherFactory());
+ /**
+ * Declare Sonar properties, for example sonar.projectKey=>foo.
+ *
+ * @see #setProperty(String, String)
+ */
+ public EmbeddedRunner addGlobalProperties(Properties p) {
+ globalProperties.putAll(p);
+ return this;
}
/**
- * Sonar is executed in an almost fully isolated classloader (mask everything by default). This method allows to unmask some classes based on
- * a prefix of their fully qualified name. It is related to the extensions provided by {@link #addExtensions(Object...)}.
- * Complex mask/unmask rules can be defined by chaining several ordered calls to {@link #unmask(String)} and {@link #mask(String)}.
- * Registered mask/unmask rules are considered in their registration order and as soon as a matching prefix is found
- * the class is masked/unmasked accordingly.
- * If no matching prefix can be found then by default class is masked.
+ * Declare a SonarQube property.
+ *
+ * @see RunnerProperties
+ * @see ScanProperties
*/
- public EmbeddedRunner unmask(String fqcnPrefix) {
- return addMaskRule("UNMASK", fqcnPrefix);
+ public EmbeddedRunner setGlobalProperty(String key, String value) {
+ globalProperties.setProperty(key, value);
+ return this;
+ }
+
+ public String globalProperty(String key, @Nullable String defaultValue) {
+ return globalProperties.getProperty(key, defaultValue);
}
/**
- * @see EmbeddedRunner#unmask(String)
+ * User-agent used in the HTTP requests to the SonarQube server
*/
- public EmbeddedRunner mask(String fqcnPrefix) {
- return addMaskRule("MASK", fqcnPrefix);
+ public EmbeddedRunner setApp(String app, String version) {
+ setGlobalProperty(InternalProperties.RUNNER_APP, app);
+ setGlobalProperty(InternalProperties.RUNNER_APP_VERSION, version);
+ return this;
+ }
+
+ public String app() {
+ return globalProperty(InternalProperties.RUNNER_APP, null);
+ }
+
+ public String appVersion() {
+ return globalProperty(InternalProperties.RUNNER_APP_VERSION, null);
}
- private EmbeddedRunner addMaskRule(String type, String fqcnPrefix) {
- String existingRules = globalProperty(MASK_RULES_PROP, "");
- if (!"".equals(existingRules)) {
- existingRules += ",";
+ public void runAnalysis(Properties analysisProperties) {
+ Properties copy = new Properties();
+ copy.putAll(analysisProperties);
+ initAnalysisProperties(copy);
+
+ String dumpToFile = copy.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE);
+ if (dumpToFile != null) {
+ File dumpFile = new File(dumpToFile);
+ Utils.writeProperties(dumpFile, copy);
+ logger.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath());
+ } else {
+ doExecute(copy);
}
- existingRules += type + "|" + fqcnPrefix;
- return setGlobalProperty(MASK_RULES_PROP, existingRules);
+ }
+
+ public void start() {
+ initGlobalDefaultValues();
+ doStart();
+ }
+
+ public void stop() {
+ doStop();
}
/**
- * @deprecated since 2.3 use {@link #unmask(String)}
+ * @deprecated since 2.5 use {@link #start()}, {@link #runAnalysis(Properties)} and then {@link #stop()}
*/
@Deprecated
- public EmbeddedRunner setUnmaskedPackages(String... packages) {
- for (String packagePrefix : packages) {
- unmask(packagePrefix + ".");
- }
- return this;
+ public final void execute() {
+ start();
+ runAnalysis(new Properties());
+ stop();
}
- public EmbeddedRunner addExtensions(Object... objects) {
- extensions.addAll(Arrays.asList(objects));
- return this;
+ private void initGlobalDefaultValues() {
+ setGlobalDefaultValue(RunnerProperties.HOST_URL, "http://localhost:9000");
+ setGlobalDefaultValue(InternalProperties.RUNNER_APP, "SonarQubeRunner");
+ setGlobalDefaultValue(InternalProperties.RUNNER_APP_VERSION, RunnerVersion.version());
}
- List<Object> extensions() {
- return extensions;
+ private void initAnalysisProperties(Properties p) {
+ initSourceEncoding(p);
+ new Dirs(logger).init(p);
+ }
+
+ void initSourceEncoding(Properties p) {
+ boolean onProject = Utils.taskRequiresProject(p);
+ if (onProject) {
+ String sourceEncoding = p.getProperty(ScanProperties.PROJECT_SOURCE_ENCODING, "");
+ boolean platformDependent = false;
+ if ("".equals(sourceEncoding)) {
+ sourceEncoding = Charset.defaultCharset().name();
+ platformDependent = true;
+ p.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, sourceEncoding);
+ }
+ logger.info("Default locale: \"" + Locale.getDefault() + "\", source code encoding: \"" + sourceEncoding + "\""
+ + (platformDependent ? " (analysis is platform dependent)" : ""));
+ }
+ }
+
+ private void setGlobalDefaultValue(String key, String value) {
+ if (!globalProperties.containsKey(key)) {
+ setGlobalProperty(key, value);
+ }
}
- @Override
protected void doStart() {
launcher = launcherFactory.createLauncher(globalProperties());
if (Utils.isAtLeast52(launcher.getVersion())) {
- launcher.start(globalProperties(), extensions, Logs.getListener());
+ launcher.start(globalProperties(), new org.sonar.runner.batch.LogOutput() {
+
+ @Override
+ public void log(String formattedMessage, Level level) {
+ logOutput.log(formattedMessage, LogOutput.Level.valueOf(level.name()));
+ }
+
+ });
}
}
- @Override
protected void doStop() {
if (Utils.isAtLeast52(launcher.getVersion())) {
launcher.stop();
}
}
- @Override
protected void doExecute(Properties analysisProperties) {
if (Utils.isAtLeast52(launcher.getVersion())) {
launcher.execute(analysisProperties);
@@ -130,7 +222,7 @@ public class EmbeddedRunner extends Runner<EmbeddedRunner> {
Properties prop = new Properties();
prop.putAll(globalProperties());
prop.putAll(analysisProperties);
- launcher.executeOldVersion(prop, extensions);
+ launcher.executeOldVersion(prop);
}
}
}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java
deleted file mode 100644
index 2a3e2b5..0000000
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/ForkedRunner.java
+++ /dev/null
@@ -1,243 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * dev@sonar.codehaus.org
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.runner.api;
-
-import org.sonar.runner.impl.Logs;
-import org.sonar.runner.impl.BatchLauncherMain;
-import org.sonar.runner.impl.JarExtractor;
-
-import javax.annotation.Nullable;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.OutputStream;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Properties;
-
-/**
- * Runner executed in a new JVM.
- *
- * @since 2.2
- */
-public class ForkedRunner extends Runner<ForkedRunner> {
-
- private static final int ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
-
- private final Map<String, String> jvmEnvVariables = new HashMap<String, String>();
- private final List<String> jvmArguments = new ArrayList<String>();
- private String javaExecutable;
- private StreamConsumer stdOut = null;
- private StreamConsumer stdErr = null;
- private final JarExtractor jarExtractor;
- private final CommandExecutor commandExecutor;
-
- private ProcessMonitor processMonitor;
-
- ForkedRunner(JarExtractor jarExtractor, CommandExecutor commandExecutor, @Nullable ProcessMonitor processMonitor) {
- this.jarExtractor = jarExtractor;
- this.commandExecutor = commandExecutor;
- this.processMonitor = processMonitor;
- }
-
- ForkedRunner(JarExtractor jarExtractor, CommandExecutor commandExecutor) {
- this(jarExtractor, commandExecutor, null);
- }
-
- /**
- * Create new instance. Never return null.
- */
- public static ForkedRunner create() {
- return new ForkedRunner(new JarExtractor(), CommandExecutor.create());
- }
-
- /**
- * Create new instance. Never return null.
- */
- public static ForkedRunner create(ProcessMonitor processMonitor) {
- return new ForkedRunner(new JarExtractor(), CommandExecutor.create(), processMonitor);
- }
-
- /**
- * Path to the java executable. The JVM of the client app is used by default
- * (see the system property java.home)
- */
- public ForkedRunner setJavaExecutable(@Nullable String s) {
- this.javaExecutable = s;
- return this;
- }
-
- public List<String> jvmArguments() {
- return new ArrayList<String>(jvmArguments);
- }
-
- /**
- * See {@link #addJvmArguments(java.util.List)}
- */
- public ForkedRunner addJvmArguments(String... s) {
- return addJvmArguments(Arrays.asList(s));
- }
-
- /**
- * JVM arguments, for example "-Xmx512m"
- */
- public ForkedRunner addJvmArguments(List<String> args) {
- jvmArguments.addAll(args);
- return this;
- }
-
- /**
- * Set a JVM environment variable. By default no variables are set.
- */
- public ForkedRunner setJvmEnvVariable(String key, String value) {
- jvmEnvVariables.put(key, value);
- return this;
- }
-
- /**
- * Add some JVM environment variables. By default no variables are set.
- */
- public ForkedRunner addJvmEnvVariables(Map<String, String> map) {
- jvmEnvVariables.putAll(map);
- return this;
- }
-
- /**
- * @deprecated Since 2.5. Use {@link ForkedRunner#setLogListener} instead.
- * Subscribe to the standard output from the forked process. By default, logs messages are sent with INFO level to the log listener set.
- */
- public ForkedRunner setStdOut(@Nullable StreamConsumer stream) {
- this.stdOut = stream;
- return this;
- }
-
- /**
- * @deprecated Since 2.5. Use {@link ForkedRunner#setLogListener}instead.
- * Subscribe to the error output from the forked process. By default, logs messages are sent with ERROR level to the log listener set.
- */
- public ForkedRunner setStdErr(@Nullable StreamConsumer stream) {
- this.stdErr = stream;
- return this;
- }
-
- @Override
- protected void doExecute(Properties props) {
- // merge both global and analysis-specific properties because it will be used both to start and to execute.
- Properties p = new Properties();
-
- p.putAll(globalProperties());
- p.putAll(props);
-
- ForkCommand forkCommand = createCommand(p);
- try {
- fork(forkCommand);
- } finally {
- deleteTempFiles(forkCommand);
- }
- }
-
- @Override
- protected void doStop() {
- // nothing to do
- }
-
- @Override
- protected void doStart() {
- // nothing to do
- }
-
- ForkCommand createCommand(Properties p) {
- File propertiesFile = writeProperties(p);
- File jarFile = jarExtractor.extractToTemp("sonar-runner-impl");
- if (javaExecutable == null) {
- javaExecutable = new Os().thisJavaExe().getAbsolutePath();
- }
- Command command = Command.builder()
- .setExecutable(javaExecutable)
- .addEnvVariables(jvmEnvVariables)
- .addArguments(jvmArguments)
- .addArguments("-cp", jarFile.getAbsolutePath(), BatchLauncherMain.class.getName(), propertiesFile.getAbsolutePath())
- .build();
- return new ForkCommand(command, jarFile, propertiesFile);
- }
-
- File writeProperties(Properties p) {
- try {
- File file = File.createTempFile("sonar-project", ".properties");
- try (OutputStream output = new FileOutputStream(file)) {
- p.store(output, "Generated by sonar-runner");
- return file;
- }
- } catch (Exception e) {
- throw new IllegalStateException("Fail to export sonar-runner properties", e);
- }
- }
-
- private static void deleteTempFiles(ForkCommand forkCommand) {
- Utils.deleteQuietly(forkCommand.jarFile);
- Utils.deleteQuietly(forkCommand.propertiesFile);
- }
-
- private void fork(ForkCommand forkCommand) {
- if (stdOut == null) {
- stdOut = new OutConsumer();
- }
- if (stdErr == null) {
- stdErr = new ErrConsumer();
- }
-
- int status = commandExecutor.execute(forkCommand.command, stdOut, stdErr, ONE_DAY_IN_MILLISECONDS, processMonitor);
- if (status != 0) {
- if (processMonitor != null && processMonitor.stop()) {
- stdOut.consumeLine(String.format("SonarQube Runner was stopped [status=%s]", status));
- } else {
- throw new IllegalStateException("Error status [command: " + forkCommand.command + "]: " + status);
- }
- }
- }
-
- private static class OutConsumer implements StreamConsumer {
- @Override
- public void consumeLine(String line) {
- Logs.info(line);
- }
- }
- private static class ErrConsumer implements StreamConsumer {
- @Override
- public void consumeLine(String line) {
- Logs.error(line);
- }
- }
-
- static class ForkCommand {
- Command command;
- File jarFile;
- File propertiesFile;
-
- private ForkCommand(Command command, File jarFile, File propertiesFile) {
- this.command = command;
- this.jarFile = jarFile;
- this.propertiesFile = propertiesFile;
- }
- }
-}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/LogListener.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/LogOutput.java
index bfcfdc4..4dfe94e 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/LogListener.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/LogOutput.java
@@ -19,8 +19,9 @@
*/
package org.sonar.runner.api;
-public interface LogListener {
- void log(String msg, Level level);
+public interface LogOutput {
+
+ void log(String formattedMessage, Level level);
enum Level {
ERROR, WARN, INFO, DEBUG, TRACE;
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java
deleted file mode 100644
index 1bd0515..0000000
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Runner.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * dev@sonar.codehaus.org
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.runner.api;
-
-import org.sonar.runner.impl.Logs;
-import org.sonar.runner.impl.InternalProperties;
-
-import javax.annotation.Nullable;
-
-import java.io.File;
-import java.util.Properties;
-
-/**
- * @since 2.2
- */
-public abstract class Runner<T extends Runner<?>> {
- private final Properties globalProperties = new Properties();
-
- protected Runner() {
- }
-
- public Properties globalProperties() {
- Properties clone = new Properties();
- clone.putAll(globalProperties);
- return clone;
- }
-
- /**
- * Set a log stream. All log events will be redirected to the listener.
- * By default, all logs are sent to stdout, except for logs of ERROR level, which are sent to stderr.
- * If null is given, the default is behavior is set.
- */
- public T setLogListener(LogListener stream) {
- Logs.setListener(new LogListenerAdapter(stream));
- return (T) this;
- }
-
- /**
- * Declare Sonar properties, for example sonar.projectKey=>foo.
- *
- * @see #setProperty(String, String)
- */
- public T addGlobalProperties(Properties p) {
- globalProperties.putAll(p);
- return (T) this;
- }
-
- /**
- * Declare a Sonar property.
- *
- * @see RunnerProperties
- * @see ScanProperties
- */
- public T setGlobalProperty(String key, String value) {
- globalProperties.setProperty(key, value);
- return (T) this;
- }
-
- public String globalProperty(String key, @Nullable String defaultValue) {
- return globalProperties.getProperty(key, defaultValue);
- }
-
- /**
- * User-agent used in the HTTP requests to the Sonar server
- */
- public T setApp(String app, String version) {
- setGlobalProperty(InternalProperties.RUNNER_APP, app);
- setGlobalProperty(InternalProperties.RUNNER_APP_VERSION, version);
- return (T) this;
- }
-
- public String app() {
- return globalProperty(InternalProperties.RUNNER_APP, null);
- }
-
- public String appVersion() {
- return globalProperty(InternalProperties.RUNNER_APP_VERSION, null);
- }
-
- public void runAnalysis(Properties analysisProperties) {
- Properties copy = new Properties();
- copy.putAll(analysisProperties);
- initAnalysisProperties(copy);
-
- String dumpToFile = copy.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE);
- if (dumpToFile != null) {
- File dumpFile = new File(dumpToFile);
- Utils.writeProperties(dumpFile, copy);
- Logs.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath());
- } else {
- doExecute(copy);
- }
- }
-
- public void start() {
- initGlobalDefaultValues();
- doStart();
- }
-
- public void stop() {
- doStop();
- }
-
- /**
- * @deprecated since 2.5 use {@link #start()}, {@link #runAnalysis(Properties)} and then {@link #stop()}
- */
- @Deprecated
- public final void execute() {
- start();
- runAnalysis(new Properties());
- stop();
- }
-
- protected abstract void doStart();
-
- protected abstract void doStop();
-
- protected abstract void doExecute(Properties analysisProperties);
-
- private void initGlobalDefaultValues() {
- setGlobalDefaultValue(RunnerProperties.HOST_URL, "http://localhost:9000");
- setGlobalDefaultValue(InternalProperties.RUNNER_APP, "SonarQubeRunner");
- setGlobalDefaultValue(InternalProperties.RUNNER_APP_VERSION, RunnerVersion.version());
- }
-
- private static void initAnalysisProperties(Properties p) {
- SourceEncoding.init(p);
- Dirs.init(p);
- }
-
- private void setGlobalDefaultValue(String key, String value) {
- if (!globalProperties.containsKey(key)) {
- setGlobalProperty(key, value);
- }
- }
-
- private class LogListenerAdapter implements org.sonar.home.log.LogListener {
- private LogListener adaptedListener;
-
- LogListenerAdapter(LogListener listener) {
- this.adaptedListener = listener;
- }
-
- @Override
- public void log(String msg, org.sonar.home.log.LogListener.Level level) {
- this.adaptedListener.log(msg, translate(level));
- }
- private LogListener.Level translate(org.sonar.home.log.LogListener.Level level) {
- switch(level) {
- case ERROR:
- return LogListener.Level.ERROR;
- case WARN:
- return LogListener.Level.WARN;
- case INFO:
- return LogListener.Level.INFO;
- case DEBUG:
- default:
- return LogListener.Level.DEBUG;
- case TRACE:
- return LogListener.Level.TRACE;
- }
- }
- }
-}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java
deleted file mode 100644
index d21f9f3..0000000
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/SourceEncoding.java
+++ /dev/null
@@ -1,47 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * dev@sonar.codehaus.org
- *
- * This program 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.
- *
- * This program 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 this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.runner.api;
-
-import org.sonar.runner.impl.Logs;
-
-import java.nio.charset.Charset;
-import java.util.Locale;
-import java.util.Properties;
-
-class SourceEncoding {
- private SourceEncoding() {
-
- }
-
- static void init(Properties p) {
- boolean onProject = Utils.taskRequiresProject(p);
- if (onProject) {
- String sourceEncoding = p.getProperty(ScanProperties.PROJECT_SOURCE_ENCODING, "");
- boolean platformDependent = false;
- if ("".equals(sourceEncoding)) {
- sourceEncoding = Charset.defaultCharset().name();
- platformDependent = true;
- p.setProperty(ScanProperties.PROJECT_SOURCE_ENCODING, sourceEncoding);
- }
- Logs.info("Default locale: \"" + Locale.getDefault() + "\", source code encoding: \"" + sourceEncoding + "\""
- + (platformDependent ? " (analysis is platform dependent)" : ""));
- }
- }
-}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandException.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/StdOutLogOutput.java
index 6c371a5..016e614 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/CommandException.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/api/StdOutLogOutput.java
@@ -19,12 +19,10 @@
*/
package org.sonar.runner.api;
-import javax.annotation.Nullable;
+public class StdOutLogOutput implements LogOutput {
-class CommandException extends RuntimeException {
-
- CommandException(String message, Command command, @Nullable Throwable throwable) {
- super(message + " [command: " + command + "]", throwable);
+ @Override
+ public void log(String formattedMessage, org.sonar.runner.api.LogOutput.Level level) {
+ System.out.println(level.name() + ": " + formattedMessage);
}
-
}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/InternalProperties.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/InternalProperties.java
new file mode 100644
index 0000000..48c3319
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/InternalProperties.java
@@ -0,0 +1,26 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+public interface InternalProperties {
+ String RUNNER_APP = "sonarRunner.app";
+ String RUNNER_APP_VERSION = "sonarRunner.appVersion";
+ String RUNNER_DUMP_TO_FILE = "sonarRunner.dumpToFile";
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedClassloader.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedClassloader.java
new file mode 100644
index 0000000..d5c52b3
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedClassloader.java
@@ -0,0 +1,111 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.Enumeration;
+import java.util.List;
+
+/**
+ * Special {@link java.net.URLClassLoader} to execute batch, which restricts loading from parent.
+ */
+class IsolatedClassloader extends URLClassLoader {
+
+ /**
+ * The parent classloader is used only for loading classes and resources in unmasked packages
+ */
+ IsolatedClassloader(ClassLoader parent) {
+ super(new URL[0], parent);
+ }
+
+ void addFiles(List<File> files) {
+ try {
+ for (File file : files) {
+ addURL(file.toURI().toURL());
+ }
+ } catch (MalformedURLException e) {
+ throw new IllegalStateException("Fail to create classloader", e);
+ }
+ }
+
+ /**
+ * Same behavior as in {@link java.net.URLClassLoader#loadClass(String, boolean)}, except loading from parent.
+ */
+ @Override
+ protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
+ // First, check if the class has already been loaded
+ Class<?> c = findLoadedClass(name);
+ if (c == null) {
+ try {
+ // Load from parent
+ if (getParent() != null && fromSonarBatchPackage(name)) {
+ c = getParent().loadClass(name);
+ } else {
+
+ // Load from system
+
+ // I don't know for other vendors, but for Oracle JVM :
+ // - ClassLoader.getSystemClassLoader() is sun.misc.Launcher$AppClassLoader. It contains app classpath.
+ // - ClassLoader.getSystemClassLoader().getParent() is sun.misc.Launcher$ExtClassLoader. It contains core JVM
+ ClassLoader systemClassLoader = getSystemClassLoader();
+ if (systemClassLoader.getParent() != null) {
+ systemClassLoader = systemClassLoader.getParent();
+ }
+ c = systemClassLoader.loadClass(name);
+ }
+ } catch (ClassNotFoundException e) {
+ // If still not found, then invoke findClass in order
+ // to find the class.
+ c = findClass(name);
+ }
+ }
+ if (resolve) {
+ resolveClass(c);
+ }
+ return c;
+ }
+
+ private boolean fromSonarBatchPackage(String name) {
+ return name.startsWith("org.sonar.runner.batch");
+ }
+
+ /**
+ * Unlike {@link java.net.URLClassLoader#getResource(String)} don't return resource from parent.
+ * See http://jira.codehaus.org/browse/SONAR-2276
+ */
+ @Override
+ public URL getResource(String name) {
+ return findResource(name);
+ }
+
+ /**
+ * Unlike {@link java.net.URLClassLoader#getResources(String)} don't return resources from parent.
+ * See http://jira.codehaus.org/browse/SONAR-2276
+ */
+ @Override
+ public Enumeration<URL> getResources(String name) throws IOException {
+ return findResources(name);
+ }
+
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
new file mode 100644
index 0000000..601776d
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
@@ -0,0 +1,94 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import java.io.File;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.List;
+import java.util.Properties;
+import org.sonar.home.cache.Logger;
+import org.sonar.home.cache.PersistentCache;
+import org.sonar.home.cache.PersistentCacheBuilder;
+import org.sonar.runner.batch.IsolatedLauncher;
+
+public class IsolatedLauncherFactory {
+ static final String ISOLATED_LAUNCHER_IMPL = "org.sonar.runner.batch.BatchIsolatedLauncher";
+ private final TempCleaning tempCleaning;
+ private final String launcherImplClassName;
+ private final Logger logger;
+
+ /**
+ * For unit tests
+ */
+ IsolatedLauncherFactory(String isolatedLauncherClassName, TempCleaning tempCleaning, Logger logger) {
+ this.tempCleaning = tempCleaning;
+ this.launcherImplClassName = isolatedLauncherClassName;
+ this.logger = logger;
+ }
+
+ public IsolatedLauncherFactory(Logger logger) {
+ this(ISOLATED_LAUNCHER_IMPL, new TempCleaning(logger), logger);
+ }
+
+ private PersistentCache getCache(Properties props) {
+ PersistentCacheBuilder builder = new PersistentCacheBuilder(logger);
+
+ if (!"true".equals(props.getProperty("sonar.enableHttpCache"))) {
+ builder.forceUpdate(true);
+ }
+
+ return builder.build();
+ }
+
+ private ClassLoader createClassLoader(List<File> jarFiles) {
+ IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader());
+ classloader.addFiles(jarFiles);
+
+ return classloader;
+ }
+
+ public IsolatedLauncher createLauncher(Properties props) {
+ ServerConnection serverConnection = ServerConnection.create(props, getCache(props), logger);
+ JarDownloader jarDownloader = new JarDownloader(serverConnection, logger);
+
+ return createLauncher(jarDownloader);
+ }
+
+ IsolatedLauncher createLauncher(final JarDownloader jarDownloader) {
+ return AccessController.doPrivileged(new PrivilegedAction<IsolatedLauncher>() {
+ @Override
+ public IsolatedLauncher run() {
+ try {
+ List<File> jarFiles = jarDownloader.download();
+ logger.debug("Create isolated classloader...");
+ ClassLoader cl = createClassLoader(jarFiles);
+ IsolatedLauncher objProxy = IsolatedLauncherProxy.create(cl, IsolatedLauncher.class, launcherImplClassName, logger);
+ tempCleaning.clean();
+
+ return objProxy;
+ } catch (Exception e) {
+ // Catch all other exceptions, which relates to reflection
+ throw new RunnerException("Unable to execute SonarQube", e);
+ }
+ }
+ });
+ }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java
new file mode 100644
index 0000000..411d731
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherProxy.java
@@ -0,0 +1,85 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import java.lang.reflect.InvocationHandler;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Proxy;
+import java.lang.reflect.UndeclaredThrowableException;
+import org.sonar.home.cache.Logger;
+
+public class IsolatedLauncherProxy implements InvocationHandler {
+ private final Object proxied;
+ private final ClassLoader cl;
+ private final Logger logger;
+
+ private IsolatedLauncherProxy(ClassLoader cl, Object proxied, Logger logger) {
+ this.cl = cl;
+ this.proxied = proxied;
+ this.logger = logger;
+ }
+
+ public static <T> T create(ClassLoader cl, Class<T> interfaceClass, String proxiedClassName, Logger logger) throws ReflectiveOperationException {
+ Object proxied = createProxiedObject(cl, proxiedClassName);
+ // interfaceClass needs to be loaded with a parent ClassLoader (common to both ClassLoaders)
+ // In addition, Proxy.newProxyInstance checks if the target ClassLoader sees the same class as the one given
+ Class<?> loadedInterfaceClass = cl.loadClass(interfaceClass.getName());
+ return (T) create(cl, proxied, loadedInterfaceClass, logger);
+ }
+
+ public static <T> T create(ClassLoader cl, Object proxied, Class<T> interfaceClass, Logger logger) {
+ Class<?>[] c = {interfaceClass};
+ return (T) Proxy.newProxyInstance(cl, c, new IsolatedLauncherProxy(cl, proxied, logger));
+ }
+
+ @Override
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
+ ClassLoader initialContextClassLoader = Thread.currentThread().getContextClassLoader();
+
+ try {
+ Thread.currentThread().setContextClassLoader(cl);
+ logger.debug("Execution " + method.getName());
+ return method.invoke(proxied, args);
+ } catch (UndeclaredThrowableException | InvocationTargetException e) {
+ throw unwrapException(e);
+ } finally {
+ Thread.currentThread().setContextClassLoader(initialContextClassLoader);
+ }
+ }
+
+ private static Throwable unwrapException(Throwable e) {
+ Throwable cause = e;
+
+ while (cause.getCause() != null) {
+ if (cause instanceof UndeclaredThrowableException || cause instanceof InvocationTargetException) {
+ cause = cause.getCause();
+ } else {
+ break;
+ }
+ }
+ return cause;
+ }
+
+ private static Object createProxiedObject(ClassLoader cl, String proxiedClassName) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
+ Class<?> proxiedClass = cl.loadClass(proxiedClassName);
+ return proxiedClass.newInstance();
+ }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java
index 168f722..62fc157 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/Os.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarDownloader.java
@@ -17,24 +17,22 @@
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.runner.api;
+package org.sonar.runner.impl;
import java.io.File;
+import java.util.List;
+import org.sonar.home.cache.Logger;
-class Os {
- boolean isWindows() {
- return System.getProperty("os.name").contains("Windows");
- }
+class JarDownloader {
+ private final ServerConnection serverConnection;
+ private final Logger logger;
- File thisJavaHome() {
- return new File(System.getProperty("java.home"));
+ JarDownloader(ServerConnection conn, Logger logger) {
+ this.serverConnection = conn;
+ this.logger = logger;
}
- /**
- * Path to the java executable used by this VM
- */
- File thisJavaExe() {
- File bin = new File(thisJavaHome(), "bin");
- return new File(bin, isWindows() ? "java.exe" : "java");
+ List<File> download() {
+ return new Jars(serverConnection, new JarExtractor(), logger).download();
}
}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarExtractor.java
index bab55a7..c4e3fa7 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/PrintStreamConsumer.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/JarExtractor.java
@@ -17,25 +17,25 @@
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.runner.api;
+package org.sonar.runner.impl;
-import java.io.PrintStream;
+import org.apache.commons.io.FileUtils;
-/**
- * Implementation of StreamConsumer that prints lines to {@link java.io.PrintStream}. Generally used
- * to forward to {@link System.out} or {@link System.err}.
- *
- * @since 2.2
- */
-public class PrintStreamConsumer implements StreamConsumer {
- final PrintStream output;
+import java.io.File;
+import java.net.URL;
- public PrintStreamConsumer(PrintStream output) {
- this.output = output;
- }
+public class JarExtractor {
- @Override
- public void consumeLine(String line) {
- output.println(line);
+ public File extractToTemp(String filenameWithoutSuffix) {
+ String filename = filenameWithoutSuffix + ".jar";
+ URL url = getClass().getResource("/" + filename);
+ try {
+ File copy = File.createTempFile(filenameWithoutSuffix, ".jar");
+ FileUtils.copyURLToFile(url, copy);
+ return copy;
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to extract " + filename, e);
+ }
}
}
+
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java
new file mode 100644
index 0000000..d181f23
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/Jars.java
@@ -0,0 +1,99 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+import org.sonar.home.cache.FileCache;
+import org.sonar.home.cache.FileCacheBuilder;
+import org.sonar.home.cache.Logger;
+
+class Jars {
+ private static final String BOOTSTRAP_INDEX_PATH = "/batch_bootstrap/index";
+ static final String BATCH_PATH = "/batch/";
+
+ private final FileCache fileCache;
+ private final ServerConnection connection;
+ private final JarExtractor jarExtractor;
+ private final Logger logger;
+
+ Jars(ServerConnection conn, JarExtractor jarExtractor, Logger logger) {
+ this.logger = logger;
+ this.fileCache = new FileCacheBuilder(logger).build();
+ this.connection = conn;
+ this.jarExtractor = jarExtractor;
+ }
+
+ /**
+ * For unit tests
+ */
+ Jars(FileCache fileCache, ServerConnection conn, JarExtractor jarExtractor, Logger logger) {
+ this.logger = logger;
+ this.fileCache = fileCache;
+ this.connection = conn;
+ this.jarExtractor = jarExtractor;
+ }
+
+ List<File> download() {
+ List<File> files = new ArrayList<File>();
+ logger.debug("Extract sonar-runner-batch in temp...");
+ files.add(jarExtractor.extractToTemp("sonar-runner-batch"));
+ files.addAll(dowloadFiles());
+ return files;
+ }
+
+ private List<File> dowloadFiles() {
+ try {
+ List<File> files = new ArrayList<File>();
+ logger.debug("Get bootstrap index...");
+ String libs = connection.downloadStringCache(BOOTSTRAP_INDEX_PATH);
+ logger.debug("Get bootstrap completed");
+ String[] lines = libs.split("[\r\n]+");
+ BatchFileDownloader batchFileDownloader = new BatchFileDownloader(connection);
+ for (String line : lines) {
+ line = line.trim();
+ if (!"".equals(line)) {
+ String[] libAndHash = line.split("\\|");
+ String filename = libAndHash[0];
+ String hash = libAndHash.length > 0 ? libAndHash[1] : "";
+ files.add(fileCache.get(filename, hash, batchFileDownloader));
+ }
+ }
+ return files;
+ } catch (Exception e) {
+ throw new IllegalStateException("Fail to download libraries from server", e);
+ }
+ }
+
+ static class BatchFileDownloader implements FileCache.Downloader {
+ private final ServerConnection connection;
+
+ BatchFileDownloader(ServerConnection conn) {
+ this.connection = conn;
+ }
+
+ @Override
+ public void download(String filename, File toFile) throws IOException {
+ connection.download(BATCH_PATH + filename, toFile);
+ }
+ }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/RunnerException.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/RunnerException.java
new file mode 100644
index 0000000..9d60947
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/RunnerException.java
@@ -0,0 +1,28 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+public class RunnerException extends RuntimeException {
+
+ public RunnerException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java
new file mode 100644
index 0000000..291ee30
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/ServerConnection.java
@@ -0,0 +1,165 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import com.github.kevinsawicki.http.HttpRequest;
+import java.io.File;
+import java.io.IOException;
+import java.net.ConnectException;
+import java.net.URL;
+import java.net.UnknownHostException;
+import java.text.MessageFormat;
+import java.util.Properties;
+import java.util.concurrent.Callable;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import org.apache.commons.io.FileUtils;
+import org.sonar.home.cache.Logger;
+import org.sonar.home.cache.PersistentCache;
+
+class ServerConnection {
+
+ private static final String SONAR_SERVER_CAN_NOT_BE_REACHED = "Sonar server ''{0}'' can not be reached";
+ private static final String STATUS_RETURNED_BY_URL_IS_INVALID = "Status returned by url : ''{0}'' is invalid : {1}";
+ static final int CONNECT_TIMEOUT_MILLISECONDS = 30000;
+ static final int READ_TIMEOUT_MILLISECONDS = 60000;
+ private static final Pattern CHARSET_PATTERN = Pattern.compile("(?i)\\bcharset=\\s*\"?([^\\s;\"]*)");
+
+ private final String serverUrl;
+ private final String userAgent;
+
+ private final PersistentCache wsCache;
+ private final boolean isModePreview;
+ private final Logger logger;
+
+ private ServerConnection(String serverUrl, String app, String appVersion, boolean preview, PersistentCache cache, Logger logger) {
+ this.logger = logger;
+ this.serverUrl = removeEndSlash(serverUrl);
+ this.userAgent = app + "/" + appVersion;
+ this.wsCache = cache;
+ this.isModePreview = preview;
+ }
+
+ private static String removeEndSlash(String url) {
+ if (url == null) {
+ return null;
+ }
+ return url.endsWith("/") ? url.substring(0, url.length() - 1) : url;
+ }
+
+ static ServerConnection create(Properties properties, PersistentCache cache, Logger logger) {
+ String serverUrl = properties.getProperty("sonar.host.url");
+ String app = properties.getProperty(InternalProperties.RUNNER_APP);
+ String appVersion = properties.getProperty(InternalProperties.RUNNER_APP_VERSION);
+ String analysisMode = properties.getProperty("sonar.analysis.mode");
+ boolean preview = "preview".equalsIgnoreCase(analysisMode);
+
+ return new ServerConnection(serverUrl, app, appVersion, preview, cache, logger);
+ }
+
+ private class StringDownloader implements Callable<String> {
+ private String url;
+
+ StringDownloader(String url) {
+ this.url = url;
+ }
+
+ @Override
+ public String call() throws Exception {
+ HttpRequest httpRequest = null;
+ try {
+ httpRequest = newHttpRequest(new URL(url));
+ String charset = getCharsetFromContentType(httpRequest.contentType());
+ if (charset == null || "".equals(charset)) {
+ charset = "UTF-8";
+ }
+ if (!httpRequest.ok()) {
+ throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, url, httpRequest.code()));
+ }
+ return httpRequest.body(charset);
+ } finally {
+ if (httpRequest != null) {
+ httpRequest.disconnect();
+ }
+ }
+ }
+ }
+
+ void download(String path, File toFile) {
+ String fullUrl = serverUrl + path;
+ try {
+ logger.debug("Download " + fullUrl + " to " + toFile.getAbsolutePath());
+ HttpRequest httpRequest = newHttpRequest(new URL(fullUrl));
+ if (!httpRequest.ok()) {
+ throw new IOException(MessageFormat.format(STATUS_RETURNED_BY_URL_IS_INVALID, fullUrl, httpRequest.code()));
+ }
+ httpRequest.receive(toFile);
+
+ } catch (Exception e) {
+ if (e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException) {
+ logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
+ }
+ FileUtils.deleteQuietly(toFile);
+ throw new IllegalStateException("Fail to download: " + fullUrl, e);
+ }
+ }
+
+ String downloadStringCache(String path) throws Exception {
+ String fullUrl = serverUrl + path;
+ try {
+ if (isModePreview) {
+ return wsCache.getString(serverUrl, new StringDownloader(fullUrl));
+ } else {
+ return new StringDownloader(fullUrl).call();
+ }
+ } catch (HttpRequest.HttpRequestException e) {
+ if (e.getCause() instanceof ConnectException || e.getCause() instanceof UnknownHostException) {
+ logger.error(MessageFormat.format(SONAR_SERVER_CAN_NOT_BE_REACHED, serverUrl));
+ }
+ throw e;
+ }
+ }
+
+ private HttpRequest newHttpRequest(URL url) {
+ HttpRequest request = HttpRequest.get(url);
+ request.trustAllCerts().trustAllHosts();
+ request.acceptGzipEncoding().uncompress(true);
+ request.connectTimeout(CONNECT_TIMEOUT_MILLISECONDS).readTimeout(READ_TIMEOUT_MILLISECONDS);
+ request.userAgent(userAgent);
+ return request;
+ }
+
+ /**
+ * Parse out a charset from a content type header.
+ *
+ * @param contentType e.g. "text/html; charset=EUC-JP"
+ * @return "EUC-JP", or null if not found. Charset is trimmed and upper-cased.
+ */
+ static String getCharsetFromContentType(String contentType) {
+ if (contentType == null) {
+ return null;
+ }
+ Matcher m = CHARSET_PATTERN.matcher(contentType);
+ if (m.find()) {
+ return m.group(1).trim().toUpperCase();
+ }
+ return null;
+ }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java
new file mode 100644
index 0000000..045e36d
--- /dev/null
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/TempCleaning.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * dev@sonar.codehaus.org
+ *
+ * This program 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.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.runner.impl;
+
+import java.io.File;
+import java.util.Collection;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.io.filefilter.AgeFileFilter;
+import org.apache.commons.io.filefilter.AndFileFilter;
+import org.apache.commons.io.filefilter.PrefixFileFilter;
+import org.sonar.home.cache.Logger;
+
+/**
+ * The file sonar-runner-batch.jar is locked by the classloader on Windows and can't be dropped at the end of the execution.
+ * See {@link IsolatedLauncherFactory}
+ */
+class TempCleaning {
+ static final int ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
+
+ final File tempDir;
+
+ private final Logger logger;
+
+ TempCleaning(Logger logger) {
+ this(new File(System.getProperty("java.io.tmpdir")), logger);
+ }
+
+ /**
+ * For unit tests
+ */
+ TempCleaning(File tempDir, Logger logger) {
+ this.logger = logger;
+ this.tempDir = tempDir;
+ }
+
+ void clean() {
+ logger.debug("Start temp cleaning...");
+ long cutoff = System.currentTimeMillis() - ONE_DAY_IN_MILLISECONDS;
+ Collection<File> files = FileUtils.listFiles(tempDir, new AndFileFilter(
+ new PrefixFileFilter("sonar-runner-batch"),
+ new AgeFileFilter(cutoff)), null);
+
+ for (File file : files) {
+ FileUtils.deleteQuietly(file);
+ }
+ logger.debug("Temp cleaning done");
+ }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/package-info.java
index a1d0d7c..d7ac772 100644
--- a/sonar-runner-api/src/main/java/org/sonar/runner/api/StreamConsumer.java
+++ b/sonar-runner-api/src/main/java/org/sonar/runner/impl/package-info.java
@@ -17,11 +17,6 @@
* License along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
*/
-package org.sonar.runner.api;
+@javax.annotation.ParametersAreNonnullByDefault
+package org.sonar.runner.impl;
-/**
- * @since 2.2
- */
-public interface StreamConsumer {
- void consumeLine(String line);
-}