]> source.dussan.org Git - sonar-scanner-cli.git/commitdiff
Back compatibility with SQ 4.5
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Thu, 27 Aug 2015 12:02:58 +0000 (14:02 +0200)
committerDuarte Meneses <duarte.meneses@sonarsource.com>
Fri, 28 Aug 2015 06:37:33 +0000 (08:37 +0200)
19 files changed:
sonar-runner-api/src/main/java/org/sonar/runner/api/EmbeddedRunner.java
sonar-runner-api/src/main/java/org/sonar/runner/api/IssueListenerAdapter.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/api/ProcessMonitor.java [deleted file]
sonar-runner-api/src/main/java/org/sonar/runner/api/Utils.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/ClassloadRules.java [new file with mode: 0644]
sonar-runner-api/src/main/java/org/sonar/runner/impl/InternalProperties.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedClassloader.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/IsolatedLauncherFactory.java
sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/api/EmbeddedRunnerTest.java
sonar-runner-api/src/test/java/org/sonar/runner/api/IssueListenerAdapterTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/api/LoggerAdapterTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/impl/ClassloadRulesTest.java [new file with mode: 0644]
sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedClassloaderTest.java
sonar-runner-api/src/test/java/org/sonar/runner/impl/IsolatedLauncherFactoryTest.java
sonar-runner-batch-interface/src/main/java/org/sonar/runner/batch/IsolatedLauncher.java
sonar-runner-batch/src/main/java/org/sonar/runner/batch/BatchIsolatedLauncher.java
sonar-runner-batch/src/test/java/org/sonar/runner/batch/IsolatedLauncherTest.java

index 5c50fa92fdb7120782d1f0102eb144144a729a1d..2a22a054ed1d832e4c0ebd468c3fae1a0b4eb294 100644 (file)
  */
 package org.sonar.runner.api;
 
-import java.io.File;
-import java.io.PrintWriter;
-import java.io.StringWriter;
+import org.sonar.runner.impl.ClassloadRules;
+
 import java.nio.charset.Charset;
 import java.security.InvalidParameterException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.List;
 import java.util.Locale;
 import java.util.Properties;
+import java.util.Set;
 
 import javax.annotation.Nullable;
 
@@ -44,12 +48,16 @@ public class EmbeddedRunner {
   private IsolatedLauncher launcher;
   private final LogOutput logOutput;
   private final Properties globalProperties = new Properties();
+  private final List<Object> extensions = new ArrayList<>();
   private final Logger logger;
+  private final Set<String> classloaderMask = new HashSet<>();
+  private final Set<String> classloaderUnmask = new HashSet<>();
 
   EmbeddedRunner(IsolatedLauncherFactory bl, Logger logger, LogOutput logOutput) {
     this.logger = logger;
     this.launcherFactory = bl;
     this.logOutput = logOutput;
+    this.classloaderUnmask.add("org.sonar.runner.batch.");
   }
 
   public static EmbeddedRunner create(final LogOutput logOutput) {
@@ -63,8 +71,22 @@ public class EmbeddedRunner {
     return clone;
   }
 
+  public EmbeddedRunner unmask(String fqcnPrefix) {
+    checkLauncherDoesntExist();
+    classloaderUnmask.add(fqcnPrefix);
+    return this;
+  }
+
+  public EmbeddedRunner mask(String fqcnPrefix) {
+    checkLauncherDoesntExist();
+    classloaderMask.add(fqcnPrefix);
+    return this;
+  }
+
   /**
    * Declare Sonar properties, for example sonar.projectKey=>foo.
+   * These might be used at different stages (on {@link #start() or #runAnalysis(Properties)}, depending on the 
+   * property and SQ version.
    *
    * @see #setProperty(String, String)
    */
@@ -75,6 +97,8 @@ public class EmbeddedRunner {
 
   /**
    * Declare a SonarQube property.
+   * These might be used at different stages (on {@link #start() or #runAnalysis(Properties)}, depending on the 
+   * property and SQ version.
    *
    * @see RunnerProperties
    * @see ScanProperties
@@ -101,30 +125,51 @@ public class EmbeddedRunner {
     return globalProperty(InternalProperties.RUNNER_APP, null);
   }
 
+  /**
+   * Add extensions to the batch's object container.
+   * Only supported until SQ 5.1. For more recent versions, an exception is thrown 
+   * @param objs
+   */
+  public EmbeddedRunner addExtensions(Object... objs) {
+    checkLauncherExists();
+    if (VersionUtils.isAtLeast52(launcher.getVersion())) {
+      throw new IllegalStateException("not supported in current SonarQube version: " + launcher.getVersion());
+    }
+
+    extensions.addAll(Arrays.asList(objs));
+    return this;
+  }
+
   public String appVersion() {
     return globalProperty(InternalProperties.RUNNER_APP_VERSION, null);
   }
 
+  /**
+   * Launch an analysis. 
+   * Runner must have been started - see {@link #start()}.
+   */
   public void runAnalysis(Properties analysisProperties) {
     runAnalysis(analysisProperties, null);
   }
 
+  /**
+   * Launch an analysis, providing optionally a issue listener.
+   * Runner must have been started - see {@link #start()}.
+   * Issue listener is supported starting in SQ 5.2. If a non-null listener is given for older versions, an exception is thrown
+   */
   public void runAnalysis(Properties analysisProperties, @Nullable IssueListener issueListener) {
     checkLauncherExists();
     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, issueListener);
-    }
+    doExecute(copy, issueListener);
   }
 
+  /**
+   * Synchronizes the project's data in the local cache with the server, allowing analysis of the project to be done offline.
+   * Runner must have been started - see {@link #start()}.
+   * Only supported starting in SQ 5.2. For older versions, an exception is thrown
+   */
   public void syncProject(String projectKey) {
     checkLauncherExists();
     if (!VersionUtils.isAtLeast52(launcher.getVersion())) {
@@ -142,16 +187,19 @@ public class EmbeddedRunner {
     doStart(forceSync);
   }
 
+  /**
+   * Stops the batch.
+   * Only supported starting in SQ 5.2. For older versions, this is a no-op.
+   */
   public void stop() {
     checkLauncherExists();
     doStop();
   }
-  
+
   public String serverVersion() {
     checkLauncherExists();
     return launcher.getVersion();
   }
-  
 
   /**
    * @deprecated since 2.5 use {@link #start()}, {@link #runAnalysis(Properties)} and then {@link #stop()}
@@ -196,7 +244,9 @@ public class EmbeddedRunner {
   }
 
   protected void doStart(boolean forceSync) {
-    launcher = launcherFactory.createLauncher(globalProperties());
+    checkLauncherDoesntExist();
+    ClassloadRules rules = new ClassloadRules(classloaderMask, classloaderUnmask);
+    launcher = launcherFactory.createLauncher(globalProperties(), rules);
     if (VersionUtils.isAtLeast52(launcher.getVersion())) {
       launcher.start(globalProperties(), new org.sonar.runner.batch.LogOutput() {
 
@@ -212,6 +262,7 @@ public class EmbeddedRunner {
   protected void doStop() {
     if (VersionUtils.isAtLeast52(launcher.getVersion())) {
       launcher.stop();
+      launcher = null;
     }
   }
 
@@ -229,80 +280,19 @@ public class EmbeddedRunner {
       Properties prop = new Properties();
       prop.putAll(globalProperties());
       prop.putAll(analysisProperties);
-      launcher.executeOldVersion(prop);
+      launcher.executeOldVersion(prop, extensions);
     }
   }
-  
+
   private void checkLauncherExists() {
-    if(launcher == null) {
+    if (launcher == null) {
       throw new IllegalStateException("not started");
     }
   }
 
-  static class IssueListenerAdapter implements org.sonar.runner.batch.IssueListener {
-    private IssueListener apiIssueListener;
-
-    public IssueListenerAdapter(IssueListener apiIssueListener) {
-      this.apiIssueListener = apiIssueListener;
-    }
-
-    @Override
-    public void handle(org.sonar.runner.batch.IssueListener.Issue issue) {
-      apiIssueListener.handle(transformIssue(issue));
-    }
-
-    private static org.sonar.runner.api.Issue transformIssue(org.sonar.runner.batch.IssueListener.Issue batchIssue) {
-      org.sonar.runner.api.Issue.Builder issueBuilder = org.sonar.runner.api.Issue.builder();
-
-      issueBuilder.setAssigneeLogin(batchIssue.getAssigneeLogin());
-      issueBuilder.setAssigneeName(batchIssue.getAssigneeName());
-      issueBuilder.setComponentKey(batchIssue.getComponentKey());
-      issueBuilder.setKey(batchIssue.getKey());
-      issueBuilder.setLine(batchIssue.getLine());
-      issueBuilder.setMessage(batchIssue.getMessage());
-      issueBuilder.setNew(batchIssue.isNew());
-      issueBuilder.setResolution(batchIssue.getResolution());
-      issueBuilder.setRuleKey(batchIssue.getRuleKey());
-      issueBuilder.setRuleName(batchIssue.getRuleName());
-      issueBuilder.setSeverity(batchIssue.getSeverity());
-      issueBuilder.setStatus(batchIssue.getStatus());
-
-      return issueBuilder.build();
-    }
-  }
-
-  private static class LoggerAdapter implements Logger {
-    private LogOutput logOutput;
-
-    LoggerAdapter(LogOutput logOutput) {
-      this.logOutput = logOutput;
-    }
-
-    @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);
+  private void checkLauncherDoesntExist() {
+    if (launcher != null) {
+      throw new IllegalStateException("already started");
     }
   }
 }
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/IssueListenerAdapter.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/IssueListenerAdapter.java
new file mode 100644 (file)
index 0000000..88b1879
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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;
+
+class IssueListenerAdapter implements org.sonar.runner.batch.IssueListener {
+  private IssueListener apiIssueListener;
+
+  public IssueListenerAdapter(IssueListener apiIssueListener) {
+    this.apiIssueListener = apiIssueListener;
+  }
+
+  @Override
+  public void handle(org.sonar.runner.batch.IssueListener.Issue issue) {
+    apiIssueListener.handle(transformIssue(issue));
+  }
+
+  private static org.sonar.runner.api.Issue transformIssue(org.sonar.runner.batch.IssueListener.Issue batchIssue) {
+    org.sonar.runner.api.Issue.Builder issueBuilder = org.sonar.runner.api.Issue.builder();
+
+    issueBuilder.setAssigneeLogin(batchIssue.getAssigneeLogin());
+    issueBuilder.setAssigneeName(batchIssue.getAssigneeName());
+    issueBuilder.setComponentKey(batchIssue.getComponentKey());
+    issueBuilder.setKey(batchIssue.getKey());
+    issueBuilder.setLine(batchIssue.getLine());
+    issueBuilder.setMessage(batchIssue.getMessage());
+    issueBuilder.setNew(batchIssue.isNew());
+    issueBuilder.setResolution(batchIssue.getResolution());
+    issueBuilder.setRuleKey(batchIssue.getRuleKey());
+    issueBuilder.setRuleName(batchIssue.getRuleName());
+    issueBuilder.setSeverity(batchIssue.getSeverity());
+    issueBuilder.setStatus(batchIssue.getStatus());
+
+    return issueBuilder.build();
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/LoggerAdapter.java
new file mode 100644 (file)
index 0000000..c3b7007
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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.home.cache.Logger;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+class LoggerAdapter implements Logger {
+  private LogOutput logOutput;
+
+  LoggerAdapter(LogOutput logOutput) {
+    this.logOutput = logOutput;
+  }
+
+  @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);
+  }
+}
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/api/ProcessMonitor.java b/sonar-runner-api/src/main/java/org/sonar/runner/api/ProcessMonitor.java
deleted file mode 100644 (file)
index cc1bcef..0000000
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * SonarQube Runner - API
- * Copyright (C) 2011 SonarSource
- * sonarqube@googlegroups.com
- *
- * 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;
-
-/**
- * To be used with {@link ForkedRunner}
- * @since 2.3
- */
-public interface ProcessMonitor {
-
-  /**
-   * {@link ForkedRunner} will poll this method periodically and if true is returned
-   * then forked SonarQube Runner process will be killed.
-   */
-  boolean stop();
-}
index 29ea62e5c9f75f1188a56e3e6fcd383e17bae187..ce855c5e1f744ccb095beb1323e74dd069cc38d6 100644 (file)
@@ -32,6 +32,7 @@ import java.nio.file.attribute.BasicFileAttributes;
 import java.util.Arrays;
 import java.util.Iterator;
 import java.util.Properties;
+
 import javax.annotation.Nullable;
 
 class Utils {
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/ClassloadRules.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/ClassloadRules.java
new file mode 100644 (file)
index 0000000..ccae3ec
--- /dev/null
@@ -0,0 +1,62 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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 javax.annotation.concurrent.Immutable;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+@Immutable
+public class ClassloadRules {
+  private final List<String> mask;
+  private final List<String> unmask;
+
+  public ClassloadRules(Set<String> maskRules, Set<String> unmaskRules) {
+    this.mask = new ArrayList<>(maskRules);
+    this.unmask = new ArrayList<>(unmaskRules);
+  }
+
+  public boolean canLoad(String className) {
+    // if there is a tie -> block it
+    return unmaskSize(className) > maskSize(className);
+  }
+
+  private int maskSize(String className) {
+    return findBestMatch(mask, className);
+  }
+
+  private int unmaskSize(String className) {
+    return findBestMatch(unmask, className);
+  }
+
+  private static int findBestMatch(List<String> list, String name) {
+    // there can be a match of 0 ("")
+    int bestMatch = -1;
+    for (String s : list) {
+      if (name.startsWith(s) && s.length() > bestMatch) {
+        bestMatch = s.length();
+      }
+    }
+
+    return bestMatch;
+  }
+}
index 03e7f22109ee50cf2dc93af0c9bd2fd35dd0d891..08ec7bb1a8d35737626d078360480872ee53f086 100644 (file)
@@ -23,4 +23,6 @@ public interface InternalProperties {
   String RUNNER_APP = "sonarRunner.app";
   String RUNNER_APP_VERSION = "sonarRunner.appVersion";
   String RUNNER_DUMP_TO_FILE = "sonarRunner.dumpToFile";
+  String RUNNER_VERSION_SIMULATION = "sonarRunner.versionSimulation";
+  String RUNNER_MASK_RULES = "sonarRunner.maskRules";
 }
index c469fd11fe474cabae693d9f4a27664b7e91380a..d273eb077adc600a7880ce6ed2f88fcaaa417d40 100644 (file)
@@ -31,12 +31,14 @@ import java.util.List;
  * Special {@link java.net.URLClassLoader} to execute batch, which restricts loading from parent.
  */
 class IsolatedClassloader extends URLClassLoader {
+  private final ClassloadRules rules;
 
   /**
    * The parent classloader is used only for loading classes and resources in unmasked packages
    */
-  IsolatedClassloader(ClassLoader parent) {
+  IsolatedClassloader(ClassLoader parent, ClassloadRules rules) {
     super(new URL[0], parent);
+    this.rules = rules;
   }
 
   void addFiles(List<File> files) {
@@ -59,7 +61,7 @@ class IsolatedClassloader extends URLClassLoader {
     if (c == null) {
       try {
         // Load from parent
-        if (getParent() != null && fromSonarBatchPackage(name)) {
+        if (getParent() != null && rules.canLoad(name)) {
           c = getParent().loadClass(name);
         } else {
 
@@ -86,10 +88,6 @@ class IsolatedClassloader extends URLClassLoader {
     return c;
   }
 
-  private static 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
index c56b72d9dc5bee81c39246e46182aad6a59f8e97..3fa467e5f4266cf0fec55a8071bdae3859bab1d9 100644 (file)
@@ -24,6 +24,7 @@ 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;
@@ -53,28 +54,35 @@ public class IsolatedLauncherFactory {
     return builder.build();
   }
 
-  private ClassLoader createClassLoader(List<File> jarFiles) {
-    IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader());
+  private ClassLoader createClassLoader(List<File> jarFiles, ClassloadRules maskRules) {
+    IsolatedClassloader classloader = new IsolatedClassloader(getClass().getClassLoader(), maskRules);
     classloader.addFiles(jarFiles);
 
     return classloader;
   }
 
-  public IsolatedLauncher createLauncher(Properties props) {
+  public IsolatedLauncher createLauncher(Properties props, ClassloadRules rules) {
+    if (props.containsKey(InternalProperties.RUNNER_DUMP_TO_FILE)) {
+      String version = props.getProperty(InternalProperties.RUNNER_VERSION_SIMULATION);
+      if (version == null) {
+        version = "5.2";
+      }
+      return new SimulatedLauncher(version, logger);
+    }
     ServerConnection serverConnection = ServerConnection.create(props, getCache(props), logger);
     JarDownloader jarDownloader = new JarDownloader(serverConnection, logger);
 
-    return createLauncher(jarDownloader);
+    return createLauncher(jarDownloader, rules);
   }
 
-  IsolatedLauncher createLauncher(final JarDownloader jarDownloader) {
+  IsolatedLauncher createLauncher(final JarDownloader jarDownloader, final ClassloadRules rules) {
     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);
+          ClassLoader cl = createClassLoader(jarFiles, rules);
           IsolatedLauncher objProxy = IsolatedLauncherProxy.create(cl, IsolatedLauncher.class, launcherImplClassName, logger);
           tempCleaning.clean();
 
diff --git a/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java b/sonar-runner-api/src/main/java/org/sonar/runner/impl/SimulatedLauncher.java
new file mode 100644 (file)
index 0000000..b4cc365
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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 org.sonar.home.cache.Logger;
+
+import javax.annotation.Nullable;
+
+import org.sonar.runner.batch.IssueListener;
+import org.sonar.runner.batch.LogOutput;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.OutputStream;
+import java.util.List;
+import java.util.Properties;
+
+import org.sonar.runner.batch.IsolatedLauncher;
+
+public class SimulatedLauncher implements IsolatedLauncher {
+  private final String version;
+  private final Logger logger;
+  private Properties globalProperties;
+
+  SimulatedLauncher(String version, Logger logger) {
+    this.version = version;
+    this.logger = logger;
+  }
+
+  @Override
+  public void start(Properties properties, LogOutput logOutput, boolean forceSync) {
+    globalProperties = properties;
+  }
+
+  @Override
+  public void stop() {
+    globalProperties = null;
+  }
+
+  @Override
+  public void execute(Properties properties) {
+    dumpProperties(globalProperties, properties);
+  }
+
+  @Override
+  public void execute(Properties properties, IssueListener listener) {
+    dumpProperties(globalProperties, properties);
+  }
+
+  private void dumpProperties(@Nullable Properties global, Properties analysis) {
+    // for old versions, analysis will have global properties merged in it
+    String filePath;
+    String filePathGlobal = null;
+    if (global != null) {
+      filePath = global.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE);
+      filePathGlobal = filePath + ".global";
+    } else {
+      filePath = analysis.getProperty(InternalProperties.RUNNER_DUMP_TO_FILE);
+    }
+
+    if (filePath == null) {
+      throw new IllegalStateException("No file to dump properties");
+    }
+
+    if (global != null) {
+      File dumpFileGlobal = new File(filePathGlobal);
+      writeProperties(dumpFileGlobal, global, "global properties");
+    }
+
+    File dumpFile = new File(filePath);
+    writeProperties(dumpFile, analysis, "analysis properties");
+    logger.info("Simulation mode. Configuration written to " + dumpFile.getAbsolutePath());
+  }
+
+  private static void writeProperties(File outputFile, Properties p, String comment) {
+    try (OutputStream output = new FileOutputStream(outputFile)) {
+      p.store(output, "Generated by sonar-runner - " + comment);
+    } catch (Exception e) {
+      throw new IllegalStateException("Fail to export sonar-runner properties", e);
+    }
+  }
+
+  @Override
+  public void syncProject(String projectKey) {
+    // no op
+  }
+
+  @Override
+  public void executeOldVersion(Properties properties, List<Object> extensions) {
+    dumpProperties(null, properties);
+  }
+
+  @Override
+  public String getVersion() {
+    return version;
+  }
+
+}
index 9a6d6766953fb628c6f1ee832bdb96d9cd4aced2..5464bd85f61abbd389a0404d271531f5fd501595 100644 (file)
@@ -19,9 +19,9 @@
  */
 package org.sonar.runner.api;
 
-import org.junit.rules.ExpectedException;
+import org.sonar.runner.impl.ClassloadRules;
 
-import org.sonar.runner.api.EmbeddedRunner.IssueListenerAdapter;
+import org.junit.rules.ExpectedException;
 
 import java.io.File;
 import java.io.FileInputStream;
@@ -31,6 +31,7 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Properties;
 
+import static org.mockito.Matchers.eq;
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -68,7 +69,7 @@ public class EmbeddedRunnerTest {
     batchLauncher = mock(IsolatedLauncherFactory.class);
     launcher = mock(IsolatedLauncher.class);
     when(launcher.getVersion()).thenReturn("5.2");
-    when(batchLauncher.createLauncher(any(Properties.class))).thenReturn(launcher);
+    when(batchLauncher.createLauncher(any(Properties.class), any(ClassloadRules.class))).thenReturn(launcher);
     runner = new EmbeddedRunner(batchLauncher, mock(Logger.class), mock(LogOutput.class));
   }
 
@@ -79,18 +80,18 @@ public class EmbeddedRunnerTest {
     runner.syncProject(projectKey);
     verify(launcher).syncProject(projectKey);
   }
-  
+
   @Test
   public void test_server_version() {
     runner.start();
     assertThat(runner.serverVersion()).isEqualTo("5.2");
   }
-  
+
   @Test
   public void test_run_before_start() {
     expectedException.expect(IllegalStateException.class);
     expectedException.expectMessage("started");
-    
+
     runner.runAnalysis(new Properties());
   }
 
@@ -130,7 +131,7 @@ public class EmbeddedRunnerTest {
       public boolean matches(Object o) {
         return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
       }
-    }));
+    }), any(ClassloadRules.class));
 
     // it should have added a few properties to analysisProperties, and have merged global props
     final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir",
@@ -147,7 +148,7 @@ public class EmbeddedRunnerTest {
         }
         return true;
       }
-    }));
+    }), eq(new LinkedList<>()));
   }
 
   @Test
@@ -179,7 +180,7 @@ public class EmbeddedRunnerTest {
       public boolean matches(Object o) {
         return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
       }
-    }));
+    }), any(ClassloadRules.class));
 
     // it should have added a few properties to analysisProperties
     final String[] mustHaveKeys = {"sonar.working.directory", "sonar.sourceEncoding", "sonar.projectBaseDir"};
@@ -247,7 +248,7 @@ public class EmbeddedRunnerTest {
       public boolean matches(Object o) {
         return "foo".equals(((Properties) o).getProperty("sonar.projectKey"));
       }
-    }));
+    }), any(ClassloadRules.class));
 
     verify(launcher).execute(argThat(new ArgumentMatcher<Properties>() {
       @Override
@@ -259,11 +260,14 @@ public class EmbeddedRunnerTest {
 
   @Test
   public void should_launch_in_simulation_mode() throws IOException {
+    batchLauncher = new IsolatedLauncherFactory(mock(Logger.class));
+    runner = new EmbeddedRunner(batchLauncher, mock(Logger.class), mock(LogOutput.class));
+
     File dump = temp.newFile();
     Properties p = new Properties();
 
     p.setProperty("sonar.projectKey", "foo");
-    p.setProperty("sonarRunner.dumpToFile", dump.getAbsolutePath());
+    runner.setGlobalProperty("sonarRunner.dumpToFile", dump.getAbsolutePath());
     runner.start();
     runner.runAnalysis(p);
     runner.stop();
@@ -282,6 +286,25 @@ public class EmbeddedRunnerTest {
     assertThat(p.getProperty("sonar.sourceEncoding", null)).isEqualTo(Charset.defaultCharset().name());
   }
 
+  @Test
+  public void invalidate_after_stop() {
+    runner.start();
+    runner.stop();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("started");
+    runner.runAnalysis(new Properties());
+  }
+  
+  @Test
+  public void cannot_start_twice() {
+    runner.start();
+
+    expectedException.expect(IllegalStateException.class);
+    expectedException.expectMessage("started");
+    runner.start();
+  }
+
   @Test
   public void should_use_parameterized_encoding() throws Exception {
     Properties p = new Properties();
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/IssueListenerAdapterTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/IssueListenerAdapterTest.java
new file mode 100644 (file)
index 0000000..0b5e2a9
--- /dev/null
@@ -0,0 +1,53 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.verify;
+
+import org.mockito.ArgumentCaptor;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.mockito.Mockito.mock;
+
+public class IssueListenerAdapterTest {
+  private IssueListenerAdapter adapter;
+  private IssueListener issueListener;
+
+  @Before
+  public void setUp() {
+    issueListener = mock(IssueListener.class);
+    adapter = new IssueListenerAdapter(issueListener);
+  }
+
+  @Test
+  public void test() {
+    org.sonar.runner.batch.IssueListener.Issue issue = new org.sonar.runner.batch.IssueListener.Issue();
+
+    issue.setAssigneeName("dummy");
+    adapter.handle(issue);
+
+    ArgumentCaptor<Issue> argument = ArgumentCaptor.forClass(Issue.class);
+    verify(issueListener).handle(argument.capture());
+
+    assertThat(argument.getValue().getAssigneeName()).isEqualTo("dummy");
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/api/LoggerAdapterTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/api/LoggerAdapterTest.java
new file mode 100644 (file)
index 0000000..00df4e9
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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 static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Matchers.eq;
+
+import static org.mockito.Matchers.startsWith;
+
+import org.junit.Test;
+import org.junit.Before;
+
+public class LoggerAdapterTest {
+  private LoggerAdapter adapter;
+  private LogOutput logOutput;
+
+  @Before
+  public void setUp() {
+    logOutput = mock(LogOutput.class);
+    adapter = new LoggerAdapter(logOutput);
+  }
+
+  @Test
+  public void testDebug() {
+    adapter.debug("debug");
+    verify(logOutput).log("debug", LogOutput.Level.DEBUG);
+    verifyNoMoreInteractions(logOutput);
+  }
+  
+  @Test
+  public void testInfo() {
+    adapter.info("info");
+    verify(logOutput).log("info", LogOutput.Level.INFO);
+    verifyNoMoreInteractions(logOutput);
+  }
+  
+  @Test
+  public void testWarn() {
+    adapter.warn("warn");
+    verify(logOutput).log("warn", LogOutput.Level.WARN);
+    verifyNoMoreInteractions(logOutput);
+  }
+  
+  @Test
+  public void testError() {
+    adapter.error("error");
+    verify(logOutput).log("error", LogOutput.Level.ERROR);
+    verifyNoMoreInteractions(logOutput);
+  }
+  
+  @Test
+  public void testErrorThrowable() {
+    adapter.error("error", new IllegalStateException("error"));
+    verify(logOutput).log(startsWith("error\njava.lang.IllegalStateException: error"), eq(LogOutput.Level.ERROR));
+    verifyNoMoreInteractions(logOutput);
+  }
+}
diff --git a/sonar-runner-api/src/test/java/org/sonar/runner/impl/ClassloadRulesTest.java b/sonar-runner-api/src/test/java/org/sonar/runner/impl/ClassloadRulesTest.java
new file mode 100644 (file)
index 0000000..b2e72fb
--- /dev/null
@@ -0,0 +1,106 @@
+/*
+ * SonarQube Runner - API
+ * Copyright (C) 2011 SonarSource
+ * sonarqube@googlegroups.com
+ *
+ * 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 static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.junit.Test;
+import org.junit.Before;
+
+public class ClassloadRulesTest {
+  private ClassloadRules rules;
+  private Set<String> maskRules;
+  private Set<String> unmaskRules;
+
+  @Before
+  public void setUp() {
+    maskRules = new HashSet<>();
+    unmaskRules = new HashSet<>();
+  }
+
+  @Test
+  public void testUnmask() {
+    unmaskRules.add("org.apache.ant.");
+    rules = new ClassloadRules(maskRules, unmaskRules);
+
+    assertThat(rules.canLoad("org.sonar.runner.Foo")).isFalse();
+    assertThat(rules.canLoad("org.objectweb.asm.ClassVisitor")).isFalse();
+    assertThat(rules.canLoad("org.apache")).isFalse();
+
+    assertThat(rules.canLoad("org.apache.ant.Foo")).isTrue();
+    assertThat(rules.canLoad("org.apache.ant.project.Project")).isTrue();
+  }
+  
+  @Test
+  public void testUnmaskAll() {
+    unmaskRules.add("");
+    rules = new ClassloadRules(maskRules, unmaskRules);
+    
+    assertThat(rules.canLoad("org.sonar.runner.Foo")).isTrue();
+    assertThat(rules.canLoad("org.objectweb.asm.ClassVisitor")).isTrue();
+    assertThat(rules.canLoad("org.apache")).isTrue();
+
+    assertThat(rules.canLoad("org.apache.ant.Foo")).isTrue();
+    assertThat(rules.canLoad("org.apache.ant.project.Project")).isTrue();
+  }
+
+  @Test
+  public void testDefault() {
+    rules = new ClassloadRules(maskRules, unmaskRules);
+    assertThat(rules.canLoad("org.sonar.runner.Foo")).isFalse();
+  }
+
+  @Test
+  public void testMaskAndUnmask() throws ClassNotFoundException {
+    unmaskRules.add("org.apache.ant.");
+    maskRules.add("org.apache.ant.foo.");
+    rules = new ClassloadRules(maskRules, unmaskRules);
+
+    assertThat(rules.canLoad("org.apache.ant.something")).isTrue();
+    assertThat(rules.canLoad("org.apache.ant.foo.something")).isFalse();
+    assertThat(rules.canLoad("org.apache")).isFalse();
+  }
+  
+  @Test
+  public void testUsedByMaven() {
+    maskRules.add( "org.slf4j.LoggerFactory" );
+    // Include slf4j Logger that is exposed by some Sonar components
+    unmaskRules.add( "org.slf4j.Logger" );
+    unmaskRules.add( "org.slf4j.ILoggerFactory" );
+    // Exclude other slf4j classes
+    // .unmask("org.slf4j.impl.")
+    maskRules.add( "org.slf4j." );
+    // Exclude logback
+    maskRules.add( "ch.qos.logback." );
+    maskRules.add( "org.sonar." );
+    unmaskRules.add("org.sonar.runner.batch.");
+    // Guava is not the same version in SonarQube classloader
+    maskRules.add( "com.google.common" );
+    // Include everything else
+    unmaskRules.add( "" );
+    
+    rules = new ClassloadRules(maskRules, unmaskRules);
+    
+    assertThat(rules.canLoad("org.sonar.runner.batch.IsolatedLauncher")).isTrue();
+  }
+}
index f2a681bae7f6c3b7f61ec704b91460087159590a..b0a9276d80441bf45babe158b56a7bfb41fceac5 100644 (file)
 package org.sonar.runner.impl;
 
 import java.io.IOException;
+import java.util.HashSet;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
-
 import static org.fest.assertions.Assertions.assertThat;
 
 public class IsolatedClassloaderTest {
@@ -35,7 +36,7 @@ public class IsolatedClassloaderTest {
     thrown.expect(ClassNotFoundException.class);
     thrown.expectMessage("org.junit.Test");
     ClassLoader parent = getClass().getClassLoader();
-    IsolatedClassloader classLoader = new IsolatedClassloader(parent);
+    IsolatedClassloader classLoader = new IsolatedClassloader(parent, new ClassloadRules(new HashSet<String>(), new HashSet<String>()));
 
     // JUnit is available in the parent classloader (classpath used to execute this test) but not in the core JVM
     assertThat(classLoader.loadClass("java.lang.String", false)).isNotNull();
index 661bee7678514179fdb4d1dc7b6d355a14c00e52..d29873ed7f58ffb33a059c2de72553919371bdbd 100644 (file)
@@ -21,6 +21,8 @@ package org.sonar.runner.impl;
 
 import org.sonar.runner.batch.IssueListener;
 
+import java.util.HashSet;
+import java.util.List;
 import java.util.Properties;
 
 import org.junit.Before;
@@ -48,7 +50,7 @@ public class IsolatedLauncherFactoryTest {
   @Test
   public void should_use_isolated_classloader() {
     try {
-      factory.createLauncher(jarDownloader);
+      factory.createLauncher(jarDownloader, new ClassloadRules(new HashSet<String>(), new HashSet<String>()));
       fail();
     } catch (RunnerException e) {
       // success
@@ -73,7 +75,7 @@ public class IsolatedLauncherFactoryTest {
     }
 
     @Override
-    public void executeOldVersion(Properties properties) {
+    public void executeOldVersion(Properties properties, List<Object> extensions) {
     }
 
     @Override
index 560f37cf41a51df8bc1846f2490388bf8591288f..7b2fffcbe09e13d4e493a1436141922cc6d5c6f4 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.runner.batch;
 
+import java.util.List;
 import java.util.Properties;
 
 public interface IsolatedLauncher {
@@ -33,7 +34,7 @@ public interface IsolatedLauncher {
   
   void syncProject(String projectKey);
   
-  void executeOldVersion(Properties properties);
+  void executeOldVersion(Properties properties, List<Object> extensions);
 
   String getVersion();
 
index f941f3293c31fce9db12a356b50b04ee504ae1f8..0cc79b73c2e6a789c93c715501c0ffda72901af4 100644 (file)
@@ -24,8 +24,10 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.nio.charset.StandardCharsets;
+import java.util.List;
 import java.util.Map;
 import java.util.Properties;
+
 import org.picocontainer.annotations.Nullable;
 import org.sonar.batch.bootstrapper.Batch;
 import org.sonar.batch.bootstrapper.EnvironmentInformation;
@@ -35,12 +37,11 @@ import org.sonar.batch.bootstrapper.EnvironmentInformation;
  * the same version of sonar-batch as the server.
  */
 public class BatchIsolatedLauncher implements IsolatedLauncher {
-
   private Batch batch = null;
 
   @Override
   public void start(Properties globalProperties, org.sonar.runner.batch.LogOutput logOutput, boolean forceSync) {
-    batch = createBatch(globalProperties, logOutput);
+    batch = createBatch(globalProperties, logOutput, null);
     batch.start(forceSync);
   }
 
@@ -65,12 +66,16 @@ public class BatchIsolatedLauncher implements IsolatedLauncher {
     batch.syncProject(projectKey);
   }
 
-  Batch createBatch(Properties properties, @Nullable final org.sonar.runner.batch.LogOutput logOutput) {
+  Batch createBatch(Properties properties, @Nullable final org.sonar.runner.batch.LogOutput logOutput, @Nullable List<Object> extensions) {
     EnvironmentInformation env = new EnvironmentInformation(properties.getProperty("sonarRunner.app"), properties.getProperty("sonarRunner.appVersion"));
     Batch.Builder builder = Batch.builder()
       .setEnvironment(env)
       .setBootstrapProperties((Map) properties);
 
+    if (extensions != null) {
+      builder.addComponents(extensions);
+    }
+
     if (logOutput != null) {
       // Do that is a separate class to avoid NoClassDefFoundError for org/sonar/batch/bootstrapper/LogOutput
       Compatibility.setLogOutputFor5dot2(builder, logOutput);
@@ -83,10 +88,10 @@ public class BatchIsolatedLauncher implements IsolatedLauncher {
    * This method exists for backward compatibility with SonarQube < 5.2. 
    */
   @Override
-  public void executeOldVersion(Properties properties) {
-    createBatch(properties, null).execute();
+  public void executeOldVersion(Properties properties, List<Object> extensions) {
+    createBatch(properties, null, extensions).execute();
   }
-  
+
   @Override
   public String getVersion() {
     InputStream is = this.getClass().getClassLoader().getResourceAsStream("sq-version.txt");
index 62f44352a733b12e24e5ac19924e4243205ec606..7ea922772602eb5f3ecfd8f4ffbe61eea5d61de4 100644 (file)
@@ -37,7 +37,7 @@ public class IsolatedLauncherTest {
     props.setProperty("sonar.projectName", "Sample");
     props.setProperty("sonar.projectVersion", "1.0");
     props.setProperty("sonar.sources", "src");
-    Batch batch = launcher.createBatch(props, null);
+    Batch batch = launcher.createBatch(props, null, null);
 
     assertThat(batch).isNotNull();
   }