]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-6517 apply feedback
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Wed, 6 May 2015 20:08:31 +0000 (22:08 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 11 May 2015 08:24:03 +0000 (10:24 +0200)
41 files changed:
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java [new file with mode: 0644]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java [deleted file]
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java [new file with mode: 0644]
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java [deleted file]
sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java [new file with mode: 0644]
sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java [deleted file]
sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java [deleted file]
sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java [new file with mode: 0644]
sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java [deleted file]
sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java

index d95084053ae8a621a2fe8d05c2dd0c19bac57ff5..1ead2800482fcf5b8ac63b5cc0c6c177dedbfef0 100644 (file)
  */
 package org.sonar.server.computation;
 
-import org.sonar.core.platform.ComponentContainer;
 import org.sonar.core.issue.db.UpdateConflictResolver;
-import org.sonar.server.computation.issue.*;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.server.computation.issue.IssueCache;
+import org.sonar.server.computation.issue.IssueComputation;
+import org.sonar.server.computation.issue.RuleCache;
+import org.sonar.server.computation.issue.RuleCacheLoader;
+import org.sonar.server.computation.issue.ScmAccountCache;
+import org.sonar.server.computation.issue.ScmAccountCacheLoader;
+import org.sonar.server.computation.issue.SourceLinesCache;
 import org.sonar.server.computation.measure.MetricCache;
 import org.sonar.server.computation.step.ComputationSteps;
 import org.sonar.server.platform.Platform;
index 7158b0d5e6d0903f7f6c1d9e5e83f84ec4c9d3b7..bf47c6455ba0f41d688d01845343bf2c2ac0e4a1 100644 (file)
@@ -46,7 +46,7 @@ public class DatabaseMigrator implements ServerComponent, Startable {
   private final ServerUpgradeStatus serverUpgradeStatus;
 
   /**
-   * ServerPluginInstaller is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
+   * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
    */
   public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
                           ServerPluginRepository unused) {
index 4f28227339e7fcdc8251739cc99b09d2ec18470a..519f13ad49cbaa01139f055f167a37fb2dbc1be3 100644 (file)
  */
 package org.sonar.server.platform;
 
-import org.sonar.core.platform.ComponentContainer;
 import org.sonar.api.platform.Server;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.persistence.DatabaseVersion;
+import org.sonar.core.platform.ComponentContainer;
 
 import javax.annotation.CheckForNull;
 import javax.servlet.ServletContext;
+
 import java.util.Collection;
 import java.util.Properties;
 
index 6844878d9b9cb7b9f635c97d32bc6617181cc541..c8594ed7438171f417591e71c7d51ae0f454ab76 100644 (file)
@@ -218,7 +218,7 @@ import org.sonar.server.plugins.InstalledPluginReferentialFactory;
 import org.sonar.server.plugins.PluginDownloader;
 import org.sonar.server.plugins.ServerExtensionInstaller;
 import org.sonar.server.plugins.ServerPluginRepository;
-import org.sonar.server.plugins.ServerPluginUnzipper;
+import org.sonar.server.plugins.ServerPluginExploder;
 import org.sonar.server.plugins.UpdateCenterClient;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.plugins.ws.AvailablePluginsWsAction;
@@ -520,7 +520,7 @@ class ServerComponents {
 
       // plugins
       ServerPluginRepository.class,
-      ServerPluginUnzipper.class,
+      ServerPluginExploder.class,
       PluginLoader.class,
       InstalledPluginReferentialFactory.class,
       ServerExtensionInstaller.class,
index b17ac9773426394891fb11a707190af68b9ed05c..96fec742509114ac1a258572b2d1c4d488116c8a 100644 (file)
@@ -47,6 +47,7 @@ import static org.apache.commons.io.FileUtils.deleteQuietly;
 import static org.apache.commons.io.FileUtils.forceMkdir;
 import static org.apache.commons.io.FileUtils.toFile;
 import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.sonar.core.platform.PluginInfo.jarToPluginInfo;
 
 /**
  * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then
@@ -115,7 +116,7 @@ public class PluginDownloader implements Startable {
    * @return the list of download plugins as {@link PluginInfo} instances
    */
   public Collection<PluginInfo> getDownloadedPlugins() {
-    return newArrayList(transform(listPlugins(this.downloadDir), PluginInfo.JarToPluginInfo.INSTANCE));
+    return newArrayList(transform(listPlugins(this.downloadDir), jarToPluginInfo()));
   }
 
   public void download(String pluginKey, Version version) {
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java
new file mode 100644 (file)
index 0000000..3c48658
--- /dev/null
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.utils.ZipUtils;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginExploder;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.server.platform.DefaultServerFileSystem;
+
+import java.io.File;
+
+import static org.apache.commons.io.FileUtils.cleanDirectory;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+
+public class ServerPluginExploder extends PluginExploder implements ServerComponent {
+
+  private final DefaultServerFileSystem fs;
+
+  public ServerPluginExploder(DefaultServerFileSystem fs) {
+    this.fs = fs;
+  }
+
+  /**
+   * JAR files of directory extensions/plugins can be moved when server is up and plugins are uninstalled.
+   * For this reason these files must not be locked by classloaders. They are copied to the directory
+   * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}.
+   */
+  @Override
+  public ExplodedPlugin explode(PluginInfo pluginInfo) {
+    File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey());
+    try {
+      forceMkdir(toDir);
+      cleanDirectory(toDir);
+
+      File jarSource = pluginInfo.getNonNullJarFile();
+      File jarTarget = new File(toDir, jarSource.getName());
+      FileUtils.copyFile(jarSource, jarTarget);
+      ZipUtils.unzip(jarSource, toDir, newLibFilter());
+      return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
+    } catch (Exception e) {
+      throw new IllegalStateException(String.format(
+        "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
+    }
+  }
+}
index 61258abec8ee2473b276de46051f8e12e8ee31e7..4b6cd9e193750eca3cb274e8a422097a1ae8ca53 100644 (file)
@@ -25,6 +25,7 @@ import com.google.common.base.Joiner;
 import com.google.common.base.Strings;
 import com.google.common.collect.ImmutableSet;
 import com.google.common.collect.Ordering;
+import java.util.HashSet;
 import org.apache.commons.io.FileUtils;
 import org.picocontainer.Startable;
 import org.sonar.api.Plugin;
@@ -42,7 +43,6 @@ import javax.annotation.Nonnull;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
@@ -57,6 +57,7 @@ import static org.apache.commons.io.FileUtils.copyFile;
 import static org.apache.commons.io.FileUtils.deleteQuietly;
 import static org.apache.commons.io.FileUtils.moveFile;
 import static org.apache.commons.io.FileUtils.moveFileToDirectory;
+import static org.sonar.core.platform.PluginInfo.jarToPluginInfo;
 
 /**
  * Manages installation and loading of plugins:
@@ -71,7 +72,7 @@ import static org.apache.commons.io.FileUtils.moveFileToDirectory;
 public class ServerPluginRepository implements PluginRepository, Startable {
 
   private static final Logger LOG = Loggers.get(ServerPluginRepository.class);
-  private static final String FILE_EXTENSION_JAR = "jar";
+  private static final String[] JAR_FILE_EXTENSIONS = new String[]{"jar"};
   private static final Set<String> DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport");
   private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
 
@@ -160,13 +161,13 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   private void registerPluginInfo(PluginInfo info) {
     if (blacklistedPluginKeys.contains(info.getKey())) {
       LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled.", info.getName(), info.getKey());
-      deleteQuietly(info.getFile());
+      deleteQuietly(info.getNonNullJarFile());
       return;
     }
     PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
     if (existing != null) {
       throw MessageException.of(format("Found two files for the same plugin [%s]: %s and %s",
-        info.getKey(), info.getFile().getName(), existing.getFile().getName()));
+        info.getKey(), info.getNonNullJarFile().getName(), existing.getNonNullJarFile().getName()));
     }
 
   }
@@ -190,15 +191,15 @@ public class ServerPluginRepository implements PluginRepository, Startable {
         copyFile(sourceFile, destFile, true);
       }
     } catch (IOException e) {
-      LOG.error(format("Fail to move or copy plugin: %s to %s",
+      throw new IllegalStateException(format("Fail to move or copy plugin: %s to %s",
         sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e);
     }
 
     PluginInfo info = PluginInfo.create(destFile);
     PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
     if (existing != null) {
-      if (!existing.getFile().getName().equals(destFile.getName())) {
-        deleteQuietly(existing.getFile());
+      if (!existing.getNonNullJarFile().getName().equals(destFile.getName())) {
+        deleteQuietly(existing.getNonNullJarFile());
       }
       LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion());
     } else {
@@ -214,41 +215,20 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   }
 
   /**
-   * Removes the plugins that are not compatible with current environment. In some cases
-   * plugin files can be deleted.
+   * Removes the plugins that are not compatible with current environment.
    */
   private void unloadIncompatiblePlugins() {
     // loop as long as the previous loop ignored some plugins. That allows to support dependencies
     // on many levels, for example D extends C, which extends B, which requires A. If A is not installed,
     // then B, C and D must be ignored. That's not possible to achieve this algorithm with a single
     // iteration over plugins.
-    List<String> removedKeys = new ArrayList<>();
+    Set<String> removedKeys = new HashSet<>();
     do {
       removedKeys.clear();
       for (PluginInfo plugin : pluginInfosByKeys.values()) {
-        if (!plugin.isCompatibleWith(server.getVersion())) {
-          throw MessageException.of(String.format(
-            "Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()));
-        }
-
-        if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !pluginInfosByKeys.containsKey(plugin.getBasePlugin())) {
-          // this plugin extends a plugin that is not installed
-          LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
+        if (!isCompatible(plugin, server, pluginInfosByKeys)) {
           removedKeys.add(plugin.getKey());
         }
-
-        for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
-          PluginInfo available = pluginInfosByKeys.get(requiredPlugin.getKey());
-          if (available == null) {
-            // this plugin requires a plugin that is not installed
-            LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
-            removedKeys.add(plugin.getKey());
-          } else if (requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(available.getVersion()) > 0) {
-            LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(),
-              requiredPlugin.getKey(), requiredPlugin.getMinimalVersion());
-            removedKeys.add(plugin.getKey());
-          }
-        }
       }
       for (String removedKey : removedKeys) {
         pluginInfosByKeys.remove(removedKey);
@@ -256,6 +236,41 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     } while (!removedKeys.isEmpty());
   }
 
+  @VisibleForTesting
+  static boolean isCompatible(PluginInfo plugin, Server server, Map<String, PluginInfo> allPluginsByKeys) {
+    if (Strings.isNullOrEmpty(plugin.getMainClass()) && Strings.isNullOrEmpty(plugin.getBasePlugin())) {
+      LOG.warn("Plugin {} [{}] is ignored because entry point class is not defined", plugin.getName(), plugin.getKey());
+      return false;
+    }
+
+    if (!plugin.isCompatibleWith(server.getVersion())) {
+      throw MessageException.of(format(
+        "Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()));
+    }
+
+    if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !allPluginsByKeys.containsKey(plugin.getBasePlugin())) {
+      // it extends a plugin that is not installed
+      LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
+      return false;
+    }
+
+    for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
+      PluginInfo available = allPluginsByKeys.get(requiredPlugin.getKey());
+      if (available == null) {
+        // it requires a plugin that is not installed
+        LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
+        return false;
+      }
+      if (requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(available.getVersion()) > 0) {
+        // it requires a more recent version
+        LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(),
+          requiredPlugin.getKey(), requiredPlugin.getMinimalVersion());
+        return false;
+      }
+    }
+    return true;
+  }
+
   private void logInstalledPlugins() {
     List<PluginInfo> orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values());
     for (PluginInfo plugin : orderedPlugins) {
@@ -264,32 +279,41 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   }
 
   private void loadInstances() {
-    pluginInstancesByKeys.clear();
     pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
   }
 
   /**
-   * Uninstall a plugin and its dependents (the plugins that require the plugin to be uninstalled)
+   * Uninstall a plugin and its dependents
    */
   public void uninstall(String pluginKey) {
-    for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
-      if (!otherPlugin.getKey().equals(pluginKey)) {
-        for (PluginInfo.RequiredPlugin requiredPlugin : otherPlugin.getRequiredPlugins()) {
-          if (requiredPlugin.getKey().equals(pluginKey)) {
-            uninstall(otherPlugin.getKey());
-          }
+    Set<String> uninstallKeys = new HashSet<>();
+    uninstallKeys.add(pluginKey);
+    appendDependentPluginKeys(pluginKey, uninstallKeys);
+
+    for (String uninstallKey : uninstallKeys) {
+      PluginInfo info = pluginInfosByKeys.get(uninstallKey);
+      if (!info.isCore()) {
+        try {
+          LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey());
+          // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
+          File masterFile = new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName());
+          moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
+        } catch (IOException e) {
+          throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e);
         }
       }
     }
+  }
 
-    PluginInfo info = pluginInfosByKeys.get(pluginKey);
-    if (!info.isCore()) {
-      try {
-        // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
-        File masterFile = new File(fs.getInstalledPluginsDir(), info.getFile().getName());
-        moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
-      } catch (IOException e) {
-        throw new IllegalStateException("Fail to uninstall plugin [" + pluginKey + "]", e);
+  private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
+    for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
+      if (!otherPlugin.getKey().equals(pluginKey)) {
+        for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
+          if (requirement.getKey().equals(pluginKey)) {
+            appendTo.add(otherPlugin.getKey());
+            appendDependentPluginKeys(otherPlugin.getKey(), appendTo);
+          }
+        }
       }
     }
   }
@@ -302,7 +326,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
    * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
    */
   public Collection<PluginInfo> getUninstalledPlugins() {
-    return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), PluginInfo.JarToPluginInfo.INSTANCE));
+    return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), jarToPluginInfo()));
   }
 
   public void cancelUninstalls() {
@@ -328,7 +352,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   public PluginInfo getPluginInfo(String key) {
     PluginInfo info = pluginInfosByKeys.get(key);
     if (info == null) {
-      throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
+      throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
     }
     return info;
   }
@@ -337,7 +361,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   public Plugin getPluginInstance(String key) {
     Plugin plugin = pluginInstancesByKeys.get(key);
     if (plugin == null) {
-      throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
+      throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
     }
     return plugin;
   }
@@ -372,7 +396,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
 
   private static Collection<File> listJarFiles(File dir) {
     if (dir.exists()) {
-      return FileUtils.listFiles(dir, new String[] {FILE_EXTENSION_JAR}, false);
+      return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false);
     }
     return Collections.emptyList();
   }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java
deleted file mode 100644 (file)
index 59bc5a8..0000000
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.server.plugins;
-
-import org.apache.commons.io.FileUtils;
-import org.sonar.api.ServerComponent;
-import org.sonar.api.utils.ZipUtils;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginUnzipper;
-import org.sonar.core.platform.UnzippedPlugin;
-import org.sonar.server.platform.DefaultServerFileSystem;
-
-import java.io.File;
-
-import static org.apache.commons.io.FileUtils.cleanDirectory;
-import static org.apache.commons.io.FileUtils.forceMkdir;
-
-public class ServerPluginUnzipper extends PluginUnzipper implements ServerComponent {
-
-  private final DefaultServerFileSystem fs;
-
-  public ServerPluginUnzipper(DefaultServerFileSystem fs) {
-    this.fs = fs;
-  }
-
-  /**
-   * JAR files of directory extensions/plugins can be moved when server is up and plugins are uninstalled.
-   * For this reason these files must not be locked by classloaders. They are copied to the directory
-   * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}.
-   */
-  @Override
-  public UnzippedPlugin unzip(PluginInfo pluginInfo) {
-    File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey());
-    try {
-      forceMkdir(toDir);
-      cleanDirectory(toDir);
-
-      File jarSource = pluginInfo.getFile();
-      File jarTarget = new File(toDir, jarSource.getName());
-      FileUtils.copyFile(jarSource, jarTarget);
-      ZipUtils.unzip(jarSource, toDir, newLibFilter());
-      return UnzippedPlugin.createFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
-    } catch (Exception e) {
-      throw new IllegalStateException(String.format(
-        "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
-    }
-  }
-}
index 730584d829b5a81ef12dd7359de7b29e01a101bf..0cd07b09c3e3ac2f9a21f7b9fe8ba469abb45389 100644 (file)
@@ -49,7 +49,7 @@ public class DebtModelPluginRepositoryTest {
 
   private static final String TEST_XML_PREFIX_PATH = "org/sonar/server/debt/DebtModelPluginRepositoryTest/";
 
-  private DebtModelPluginRepository modelFinder;
+  DebtModelPluginRepository underTest;
 
   @Test
   public void test_component_initialization() throws Exception {
@@ -63,13 +63,13 @@ public class DebtModelPluginRepositoryTest {
     when(repository.getPluginInfos()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
     FakePlugin fakePlugin = new FakePlugin();
     when(repository.getPluginInstance(anyString())).thenReturn(fakePlugin);
-    modelFinder = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);
+    underTest = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);
 
     // when
-    modelFinder.start();
+    underTest.start();
 
     // assert
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    Collection<String> contributingPluginList = underTest.getContributingPluginList();
     assertThat(contributingPluginList.size()).isEqualTo(2);
     assertThat(contributingPluginList).containsOnly("technical-debt", "csharp");
   }
@@ -77,7 +77,7 @@ public class DebtModelPluginRepositoryTest {
   @Test
   public void contributing_plugin_list() {
     initModel();
-    Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
+    Collection<String> contributingPluginList = underTest.getContributingPluginList();
     assertThat(contributingPluginList.size()).isEqualTo(2);
     assertThat(contributingPluginList).contains("csharp", "java");
   }
@@ -87,7 +87,7 @@ public class DebtModelPluginRepositoryTest {
     initModel();
     Reader xmlFileReader = null;
     try {
-      xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
+      xmlFileReader = underTest.createReaderForXMLFile("csharp");
       assertNotNull(xmlFileReader);
       List<String> lines = IOUtils.readLines(xmlFileReader);
       assertThat(lines.size()).isEqualTo(25);
@@ -102,21 +102,28 @@ public class DebtModelPluginRepositoryTest {
   @Test
   public void return_xml_file_path_for_plugin() {
     initModel();
-    assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
+    assertThat(underTest.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
   }
 
   @Test
+<<<<<<< HEAD
   public void contain_default_model() {
     modelFinder = new DebtModelPluginRepository(mock(PluginRepository.class));
     modelFinder.start();
     assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
+=======
+  public void contain_default_model() throws Exception {
+    underTest = new DebtModelPluginRepository(mock(PluginRepository.class));
+    underTest.start();
+    assertThat(underTest.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
+>>>>>>> SONAR-6517 apply feedback
   }
 
   private void initModel() {
     Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
     contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
     contributingPluginKeyToClassLoader.put("java", newClassLoader());
-    modelFinder = new DebtModelPluginRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
+    underTest = new DebtModelPluginRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
   }
 
   private ClassLoader newClassLoader() {
index 58c0e12c4498071371cef55abac1539ee7211fdf..b027737c11611ef7b89ba8f1c9915ae8a0e56a59 100644 (file)
@@ -23,6 +23,8 @@ import org.junit.Test;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginRepository;
 
+import java.io.IOException;
+
 import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -31,7 +33,7 @@ import static org.mockito.Mockito.when;
 public class InstalledPluginReferentialFactoryTest {
 
   @Test
-  public void should_create_plugin_referential() {
+  public void should_create_plugin_referential() throws IOException {
     PluginInfo info = new PluginInfo("foo");
     PluginRepository pluginRepository = mock(PluginRepository.class);
     when(pluginRepository.getPluginInfos()).thenReturn(newArrayList(info));
index c7891203caae4bf85a4f4470763c603839274cc5..8afe4e72f6230d96abd9e8cabdd9e3820f4e0002 100644 (file)
@@ -23,13 +23,15 @@ import org.junit.Test;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.updatecenter.common.PluginReferential;
 
+import java.io.IOException;
+
 import static com.google.common.collect.Lists.newArrayList;
 import static org.assertj.core.api.Assertions.assertThat;
 
 public class PluginReferentialMetadataConverterTest {
 
   @Test
-  public void should_convert_info_to_plugin_referential() {
+  public void should_convert_info_to_plugin_referential() throws IOException {
     PluginInfo info = new PluginInfo("foo");
 
     PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));
@@ -39,7 +41,7 @@ public class PluginReferentialMetadataConverterTest {
   }
 
   @Test
-  public void should_not_add_core_plugin() {
+  public void should_not_add_core_plugin() throws IOException {
     PluginInfo info = new PluginInfo("foo").setCore(true);
 
     PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java
new file mode 100644 (file)
index 0000000..d67cbde
--- /dev/null
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.server.platform.DefaultServerFileSystem;
+
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ServerPluginExploderTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class);
+  ServerPluginExploder underTest = new ServerPluginExploder(fs);
+
+  @Test
+  public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
+    File deployDir = temp.newFolder();
+    when(fs.getDeployedPluginsDir()).thenReturn(deployDir);
+    File jar = TestProjectUtils.jarOf("test-libs-plugin");
+    PluginInfo info = PluginInfo.create(jar);
+
+    ExplodedPlugin exploded = underTest.explode(info);
+
+    // all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory
+    // web/deploy/{pluginKey}
+    File pluginDeployDir = new File(deployDir, "testlibs");
+
+    assertThat(exploded.getKey()).isEqualTo("testlibs");
+    assertThat(exploded.getMain()).isFile().exists().hasParent(pluginDeployDir);
+    assertThat(exploded.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar");
+    for (File lib : exploded.getLibs()) {
+      assertThat(lib).exists().isFile();
+      assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
+    }
+  }
+}
index 8046379bca23fac09d371c2eda690fca178aef44..0e114a78f75059ec07fa2625bb094ade4a3bb58d 100644 (file)
  */
 package org.sonar.server.plugins;
 
+import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.ImmutableSet;
+import java.util.Collections;
+import java.util.Map;
 import org.apache.commons.io.FileUtils;
 import org.junit.After;
 import org.junit.Before;
@@ -30,6 +33,8 @@ import org.mockito.Mockito;
 import org.sonar.api.platform.Server;
 import org.sonar.api.platform.ServerUpgradeStatus;
 import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginLoader;
 import org.sonar.server.platform.DefaultServerFileSystem;
 import org.sonar.updatecenter.common.Version;
@@ -47,10 +52,13 @@ public class ServerPluginRepositoryTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
+  @Rule
+  public LogTester logs = new LogTester();
+
   Server server = mock(Server.class);
   ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
   DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS);
-  PluginLoader pluginLoader = new PluginLoader(new ServerPluginUnzipper(fs));
+  PluginLoader pluginLoader = new PluginLoader(new ServerPluginExploder(fs));
   ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader);
 
   @Before
@@ -293,7 +301,7 @@ public class ServerPluginRepositoryTest {
   }
 
   @Test
-  public void fail_is_missing_required_plugin() throws Exception {
+  public void fail_to_get_missing_plugins() throws Exception {
     try {
       underTest.getPluginInfo("unknown");
       fail();
@@ -309,6 +317,25 @@ public class ServerPluginRepositoryTest {
     }
   }
 
+  @Test
+  public void plugin_is_incompatible_if_no_entry_point_class() throws Exception {
+    PluginInfo plugin = new PluginInfo("foo").setName("Foo");
+    assertThat(ServerPluginRepository.isCompatible(plugin, server, Collections.<String, PluginInfo>emptyMap())).isFalse();
+    assertThat(logs.logs()).contains("Plugin Foo [foo] is ignored because entry point class is not defined");
+  }
+
+  /**
+   * Some plugins can only extend the classloader of base plugin, without declaring new extensions.
+   */
+  @Test
+  public void plugin_is_compatible_if_no_entry_point_class_but_extend_other_plugin() throws Exception {
+    PluginInfo basePlugin = new PluginInfo("base").setMainClass("org.bar.Bar");
+    PluginInfo plugin = new PluginInfo("foo").setBasePlugin("base");
+    Map<String, PluginInfo> plugins = ImmutableMap.of("base", basePlugin, "foo", plugin);
+
+    assertThat(ServerPluginRepository.isCompatible(plugin, server, plugins)).isTrue();
+  }
+
   private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
     File jar = TestProjectUtils.jarOf(testPluginName);
     // file is copied because it's supposed to be moved by the test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java
deleted file mode 100644 (file)
index efea25d..0000000
+++ /dev/null
@@ -1,64 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.server.plugins;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.UnzippedPlugin;
-import org.sonar.server.platform.DefaultServerFileSystem;
-
-import java.io.File;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ServerPluginUnzipperTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class);
-  ServerPluginUnzipper underTest = new ServerPluginUnzipper(fs);
-
-  @Test
-  public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
-    File deployDir = temp.newFolder();
-    when(fs.getDeployedPluginsDir()).thenReturn(deployDir);
-    File jar = TestProjectUtils.jarOf("test-libs-plugin");
-    PluginInfo info = PluginInfo.create(jar);
-
-    UnzippedPlugin unzipped = underTest.unzip(info);
-
-    // all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory
-    // web/deploy/{pluginKey}
-    File pluginDeployDir = new File(deployDir, "testlibs");
-
-    assertThat(unzipped.getKey()).isEqualTo("testlibs");
-    assertThat(unzipped.getMain()).isFile().exists().hasParent(pluginDeployDir);
-    assertThat(unzipped.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar");
-    for (File lib : unzipped.getLibs()) {
-      assertThat(lib).exists().isFile();
-      assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
-    }
-  }
-}
index 11a544e1d2342bf66a8a836787f4ade228f11849..1279f978501fb702c7308538cbb1c687ae0c1030 100644 (file)
@@ -19,7 +19,9 @@
  */
 package org.sonar.server.plugins.ws;
 
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.core.platform.PluginInfo;
@@ -42,12 +44,14 @@ public class InstalledPluginsWsActionTest {
       "  \"plugins\":" + "[]" +
       "}";
 
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
   private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
   private InstalledPluginsWsAction underTest = new InstalledPluginsWsAction(pluginRepository, new PluginWSCommons());
 
   private Request request = mock(Request.class);
   private WsTester.TestResponse response = new WsTester.TestResponse();
-  private PluginInfo corePlugin = corePlugin("core1", "1.0");
 
   @Test
   public void action_installed_is_defined() {
@@ -75,7 +79,7 @@ public class InstalledPluginsWsActionTest {
 
   @Test
   public void core_plugin_are_not_returned() throws Exception {
-    when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin));
+    when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin("core1", "1.0")));
 
     underTest.handle(request, response);
 
@@ -99,7 +103,9 @@ public class InstalledPluginsWsActionTest {
   public void verify_properties_displayed_in_json_per_plugin() throws Exception {
     String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
     when(pluginRepository.getPluginInfos()).thenReturn(of(
-      new PluginInfo("plugKey").setName("plugName").setCore(false)
+      new PluginInfo("plugKey")
+        .setName("plugName")
+        .setCore(false)
         .setDescription("desc_it")
         .setVersion(Version.create("1.0"))
         .setLicense("license_hey")
@@ -107,8 +113,8 @@ public class InstalledPluginsWsActionTest {
         .setOrganizationUrl("org_url")
         .setHomepageUrl("homepage_url")
         .setIssueTrackerUrl("issueTracker_url")
-        .setFile(new File(getClass().getResource(jarFilename).toURI()))
         .setImplementationBuild("sou_rev_sha1")
+        .setJarFile(new File(getClass().getResource(jarFilename).toURI()))
       )
       );
 
@@ -183,15 +189,11 @@ public class InstalledPluginsWsActionTest {
     assertThat(response.outputAsString()).containsOnlyOnce("name2");
   }
 
-  private static PluginInfo corePlugin(String key, String version) {
+  private PluginInfo corePlugin(String key, String version) {
     return new PluginInfo(key).setName(key).setCore(true).setVersion(Version.create(version));
   }
 
-  private static PluginInfo plugin(String key, String name, String version) {
-    return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create(version));
-  }
-
-  private static PluginInfo plugin(String key, String name) {
+  private PluginInfo plugin(String key, String name) {
     return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create("1.0"));
   }
 }
index 1849151312af7bbe63d497d6017407427942a691..17586f0ec1873d5ab586c9a013ffdcc1758cc4ac 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.plugins.ws;
 
-import java.io.File;
 import org.junit.Test;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
@@ -29,6 +28,8 @@ import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.ws.WsTester;
 import org.sonar.updatecenter.common.Version;
 
+import java.io.IOException;
+
 import static com.google.common.collect.ImmutableList.of;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
@@ -37,28 +38,13 @@ import static org.sonar.test.JsonAssert.assertJson;
 
 public class PendingPluginsWsActionTest {
 
-  public static final PluginInfo GIT_PLUGIN_INFO = new PluginInfo("scmgit")
-    .setName("Git")
-    .setDescription("Git SCM Provider.")
-    .setVersion(Version.create("1.0"))
-    .setLicense("GNU LGPL 3")
-    .setOrganizationName("SonarSource")
-    .setOrganizationUrl("http://www.sonarsource.com")
-    .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
-    .setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
-    .setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"))
-    .setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07");
   private static final String DUMMY_CONTROLLER_KEY = "dummy";
-  public static final PluginInfo PLUGIN_2_2 = new PluginInfo("key2").setName("name2");
-  public static final PluginInfo PLUGIN_2_1 = new PluginInfo("key1").setName("name2");
-  public static final PluginInfo PLUGIN_0_0 = new PluginInfo("key0").setName("name0");
-
-  private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
-  private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
-  private PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());
 
-  private Request request = mock(Request.class);
-  private WsTester.TestResponse response = new WsTester.TestResponse();
+  PluginDownloader pluginDownloader = mock(PluginDownloader.class);
+  ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
+  PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());
+  Request request = mock(Request.class);
+  WsTester.TestResponse response = new WsTester.TestResponse();
 
   @Test
   public void action_pending_is_defined() {
@@ -91,7 +77,7 @@ public class PendingPluginsWsActionTest {
 
   @Test
   public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception {
-    when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
+    when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(gitPluginInfo()));
 
     underTest.handle(request, response);
 
@@ -119,7 +105,7 @@ public class PendingPluginsWsActionTest {
 
   @Test
   public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception {
-    when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
+    when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(gitPluginInfo()));
 
     underTest.handle(request, response);
 
@@ -146,12 +132,11 @@ public class PendingPluginsWsActionTest {
   }
 
   @Test
-  public void installing_plugin_are_sorted_by_name_then_key_and_are_unique() throws Exception {
+  public void installing_plugins_are_sorted_by_name_then_key_and_are_unique() throws Exception {
     when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(
-      PLUGIN_2_2,
-      PLUGIN_2_1,
-      PLUGIN_2_2,
-      PLUGIN_0_0
+      newPluginInfo(0).setName("Foo"),
+      newPluginInfo(3).setName("Bar"),
+      newPluginInfo(2).setName("Bar")
       ));
 
     underTest.handle(request, response);
@@ -161,16 +146,16 @@ public class PendingPluginsWsActionTest {
         "  \"installing\": " +
         "  [" +
         "    {" +
-        "      \"key\": \"key0\"," +
-        "      \"name\": \"name0\"," +
+        "      \"key\": \"key2\"," +
+        "      \"name\": \"Bar\"," +
         "    }," +
         "    {" +
-        "      \"key\": \"key1\"," +
-        "      \"name\": \"name2\"," +
+        "      \"key\": \"key3\"," +
+        "      \"name\": \"Bar\"," +
         "    }," +
         "    {" +
-        "      \"key\": \"key2\"," +
-        "      \"name\": \"name2\"," +
+        "      \"key\": \"key0\"," +
+        "      \"name\": \"Foo\"," +
         "    }" +
         "  ]," +
         "  \"removing\": []" +
@@ -179,12 +164,11 @@ public class PendingPluginsWsActionTest {
   }
 
   @Test
-  public void removing_plugin_are_sorted_and_unique() throws Exception {
+  public void removing_plugins_are_sorted_and_unique() throws Exception {
     when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(
-      PLUGIN_2_2,
-      PLUGIN_2_1,
-      PLUGIN_2_2,
-      PLUGIN_0_0
+      newPluginInfo(0).setName("Foo"),
+      newPluginInfo(3).setName("Bar"),
+      newPluginInfo(2).setName("Bar")
       ));
 
     underTest.handle(request, response);
@@ -195,19 +179,36 @@ public class PendingPluginsWsActionTest {
         "  \"removing\": " +
         "  [" +
         "    {" +
-        "      \"key\": \"key0\"," +
-        "      \"name\": \"name0\"," +
+        "      \"key\": \"key2\"," +
+        "      \"name\": \"Bar\"," +
         "    }," +
         "    {" +
-        "      \"key\": \"key1\"," +
-        "      \"name\": \"name2\"," +
+        "      \"key\": \"key3\"," +
+        "      \"name\": \"Bar\"," +
         "    }," +
         "    {" +
-        "      \"key\": \"key2\"," +
-        "      \"name\": \"name2\"," +
+        "      \"key\": \"key0\"," +
+        "      \"name\": \"Foo\"," +
         "    }" +
         "  ]" +
         "}"
       );
   }
+
+  public PluginInfo gitPluginInfo() {
+    return new PluginInfo("scmgit")
+      .setName("Git")
+      .setDescription("Git SCM Provider.")
+      .setVersion(Version.create("1.0"))
+      .setLicense("GNU LGPL 3")
+      .setOrganizationName("SonarSource")
+      .setOrganizationUrl("http://www.sonarsource.com")
+      .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
+      .setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
+      .setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07");
+  }
+
+  public PluginInfo newPluginInfo(int id) throws IOException {
+    return new PluginInfo("key" + id).setName("name" + id);
+  }
 }
index e055635b343f9cf1e4d0eb068ed934ca089e6bf6..c7e5151d57ef2fc112d599e8a18dd368f392f076 100644 (file)
@@ -19,7 +19,6 @@
  */
 package org.sonar.server.plugins.ws;
 
-import java.io.File;
 import org.junit.Test;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
@@ -39,36 +38,14 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE;
 import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UPGRADE;
 
 public class PluginWSCommonsTest {
-  private static final PluginInfo GIT_PLUGIN_METADATA = new PluginInfo("scmgit")
-    .setName("Git")
-    .setDescription("Git SCM Provider.")
-    .setVersion(Version.create("1.0"))
-    .setLicense("GNU LGPL 3")
-    .setOrganizationName("SonarSource")
-    .setOrganizationUrl("http://www.sonarsource.com")
-    .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
-    .setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
-    .setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"));
-  private static final Plugin PLUGIN = new Plugin("p_key")
-    .setName("p_name")
-    .setCategory("p_category")
-    .setDescription("p_description")
-    .setLicense("p_license")
-    .setOrganization("p_orga_name")
-    .setOrganizationUrl("p_orga_url")
-    .setTermsConditionsUrl("p_t_and_c_url");
-  private static final Release RELEASE = new Release(PLUGIN, version("1.0")).setDate(parseDate("2015-04-16"))
-    .setDownloadUrl("http://toto.com/file.jar")
-    .setDescription("release description")
-    .setChangelogUrl("http://change.org/plugin");
-
-  private WsTester.TestResponse response = new WsTester.TestResponse();
-  private JsonWriter jsonWriter = response.newJsonWriter();
-  private PluginWSCommons underTest = new PluginWSCommons();
+
+  WsTester.TestResponse response = new WsTester.TestResponse();
+  JsonWriter jsonWriter = response.newJsonWriter();
+  PluginWSCommons underTest = new PluginWSCommons();
 
   @Test
   public void verify_properties_written_by_writePluginMetadata() {
-    underTest.writePluginMetadata(jsonWriter, GIT_PLUGIN_METADATA);
+    underTest.writePluginMetadata(jsonWriter, gitPluginInfo());
 
     jsonWriter.close();
     assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo("{" +
@@ -87,7 +64,7 @@ public class PluginWSCommonsTest {
   @Test
   public void verify_properties_written_by_writeMetadata() {
     jsonWriter.beginObject();
-    underTest.writeMetadata(jsonWriter, GIT_PLUGIN_METADATA);
+    underTest.writeMetadata(jsonWriter, gitPluginInfo());
     jsonWriter.endObject();
 
     jsonWriter.close();
@@ -106,7 +83,7 @@ public class PluginWSCommonsTest {
 
   @Test
   public void verify_properties_written_by_writePluginUpdate() {
-    underTest.writePluginUpdate(jsonWriter, PluginUpdate.createForPluginRelease(RELEASE, version("1.0")));
+    underTest.writePluginUpdate(jsonWriter, PluginUpdate.createForPluginRelease(newRelease(), version("1.0")));
 
     jsonWriter.close();
     assertJson(response.outputAsString()).isSimilarTo("{" +
@@ -128,7 +105,7 @@ public class PluginWSCommonsTest {
   @Test
   public void verify_properties_written_by_writeMetadata_from_plugin() {
     jsonWriter.beginObject();
-    underTest.writeMetadata(jsonWriter, PLUGIN);
+    underTest.writeMetadata(jsonWriter, newPlugin());
     jsonWriter.endObject();
 
     jsonWriter.close();
@@ -147,7 +124,7 @@ public class PluginWSCommonsTest {
   @Test
   public void writeRelease() {
     jsonWriter.beginObject();
-    underTest.writeRelease(jsonWriter, RELEASE);
+    underTest.writeRelease(jsonWriter, newRelease());
     jsonWriter.endObject();
 
     jsonWriter.close();
@@ -197,11 +174,11 @@ public class PluginWSCommonsTest {
   }
 
   @Test
-  public void writeUpdate_renders_key_name_and_description_of_outgoing_dependencies() {
+  public void writeUpdate_renders_key_name_and_description_of_requirements() {
     PluginUpdate pluginUpdate = new PluginUpdate();
     pluginUpdate.setRelease(
-      new Release(PLUGIN, version("1.0")).addOutgoingDependency(RELEASE)
-    );
+      new Release(newPlugin(), version("1.0")).addOutgoingDependency(newRelease())
+      );
 
     jsonWriter.beginObject();
     underTest.writeUpdate(jsonWriter, pluginUpdate);
@@ -228,4 +205,35 @@ public class PluginWSCommonsTest {
   private static Release release(String key) {
     return new Release(new Plugin(key), version("1.0"));
   }
+
+  private PluginInfo gitPluginInfo() {
+    return new PluginInfo("scmgit")
+      .setName("Git")
+      .setDescription("Git SCM Provider.")
+      .setVersion(Version.create("1.0"))
+      .setLicense("GNU LGPL 3")
+      .setOrganizationName("SonarSource")
+      .setOrganizationUrl("http://www.sonarsource.com")
+      .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
+      .setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT");
+  }
+
+  private Plugin newPlugin() {
+    return new Plugin("p_key")
+      .setName("p_name")
+      .setCategory("p_category")
+      .setDescription("p_description")
+      .setLicense("p_license")
+      .setOrganization("p_orga_name")
+      .setOrganizationUrl("p_orga_url")
+      .setTermsConditionsUrl("p_t_and_c_url");
+  }
+
+  private Release newRelease() {
+    return new Release(newPlugin(), version("1.0")).setDate(parseDate("2015-04-16"))
+      .setDownloadUrl("http://toto.com/file.jar")
+      .setDescription("release description")
+      .setChangelogUrl("http://change.org/plugin");
+  }
+
 }
index 8af9cb1eb7c8a25d34ba84e306387bd76325c8fe..8ae87df6f404982ace4f9882283b155c94f5924d 100644 (file)
@@ -70,6 +70,6 @@ public class GeneratePluginIndexTest {
   }
 
   private PluginInfo newInfo(String pluginKey) throws IOException {
-    return new PluginInfo(pluginKey).setFile(temp.newFile(pluginKey + ".jar"));
+    return new PluginInfo(pluginKey).setJarFile(temp.newFile(pluginKey + ".jar"));
   }
 }
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java
new file mode 100644 (file)
index 0000000..3ecb3e2
--- /dev/null
@@ -0,0 +1,77 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.BatchComponent;
+import org.sonar.api.utils.ZipUtils;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginExploder;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.home.cache.FileCache;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+public class BatchPluginExploder extends PluginExploder implements BatchComponent {
+
+  private final FileCache fileCache;
+
+  public BatchPluginExploder(FileCache fileCache) {
+    this.fileCache = fileCache;
+  }
+
+  @Override
+  public ExplodedPlugin explode(PluginInfo info) {
+    try {
+      File dir = unzipFile(info.getNonNullJarFile());
+      return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), dir);
+    } catch (Exception e) {
+      throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getNonNullJarFile().getAbsolutePath()), e);
+    }
+  }
+
+  private File unzipFile(File cachedFile) throws IOException {
+    String filename = cachedFile.getName();
+    File destDir = new File(cachedFile.getParentFile(), filename + "_unzip");
+    File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock");
+    if (!destDir.exists()) {
+      FileOutputStream out = new FileOutputStream(lockFile);
+      try {
+        java.nio.channels.FileLock lock = out.getChannel().lock();
+        try {
+          // Recheck in case of concurrent processes
+          if (!destDir.exists()) {
+            File tempDir = fileCache.createTempDir();
+            ZipUtils.unzip(cachedFile, tempDir, newLibFilter());
+            FileUtils.moveDirectory(tempDir, destDir);
+          }
+        } finally {
+          lock.release();
+        }
+      } finally {
+        out.close();
+        FileUtils.deleteQuietly(lockFile);
+      }
+    }
+    return destDir;
+  }
+}
index 6e2c5886c605f83931d347271acf2e6555e7d6db..190ad8a5e8267be7b734dd331a73f8504fe30972 100644 (file)
@@ -45,6 +45,7 @@ import java.util.Map;
 public class BatchPluginInstaller implements PluginInstaller {
 
   private static final Logger LOG = Loggers.get(BatchPluginInstaller.class);
+  private static final String PLUGINS_INDEX_URL = "/deploy/plugins/index.txt";
 
   private final ServerClient server;
   private final FileCache fileCache;
@@ -105,10 +106,9 @@ public class BatchPluginInstaller implements PluginInstaller {
    */
   @VisibleForTesting
   List<RemotePlugin> listRemotePlugins() {
-    String url = "/deploy/plugins/index.txt";
     try {
       LOG.debug("Download index of plugins");
-      String indexContent = server.request(url);
+      String indexContent = server.request(PLUGINS_INDEX_URL);
       String[] rows = StringUtils.split(indexContent, CharUtils.LF);
       List<RemotePlugin> result = Lists.newArrayList();
       for (String row : rows) {
@@ -117,7 +117,7 @@ public class BatchPluginInstaller implements PluginInstaller {
       return result;
 
     } catch (Exception e) {
-      throw new IllegalStateException("Fail to download list of plugins: " + url, e);
+      throw new IllegalStateException("Fail to download list of plugins: " + PLUGINS_INDEX_URL, e);
     }
   }
 }
index 09cb7d391f0392b073d143e4db621553f488ae42..0cd7f1c62e7e5050e697c24b02814dce72cd0c71 100644 (file)
@@ -48,6 +48,7 @@ public class BatchPluginPredicate implements Predicate<String>, BatchComponent {
   private static final String CORE_PLUGIN_KEY = "core";
   private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker";
   private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead.";
+  private static final Joiner COMMA_JOINER = Joiner.on(", ");
 
   private final Set<String> whites = newHashSet(), blacks = newHashSet();
   private final DefaultAnalysisMode mode;
@@ -75,10 +76,10 @@ public class BatchPluginPredicate implements Predicate<String>, BatchComponent {
       }
     }
     if (!whites.isEmpty()) {
-      LOG.info("Include plugins: " + Joiner.on(", ").join(whites));
+      LOG.info("Include plugins: " + COMMA_JOINER.join(whites));
     }
     if (!blacks.isEmpty()) {
-      LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks));
+      LOG.info("Exclude plugins: " + COMMA_JOINER.join(blacks));
     }
   }
 
index b20c85114ed8f19a895c9043a8fbe6e952cac34d..37658fe8b1ea73ebe8689f8f225f77f4cd8772a4 100644 (file)
@@ -19,6 +19,9 @@
  */
 package org.sonar.batch.bootstrap;
 
+import com.google.common.base.Preconditions;
+import com.google.common.collect.Maps;
+import java.util.HashMap;
 import org.picocontainer.Startable;
 import org.sonar.api.Plugin;
 import org.sonar.core.platform.PluginInfo;
@@ -28,6 +31,9 @@ import org.sonar.core.platform.PluginRepository;
 import java.util.Collection;
 import java.util.Map;
 
+/**
+ * Orchestrates the installation and loading of plugins
+ */
 public class BatchPluginRepository implements PluginRepository, Startable {
 
   private final PluginInstaller installer;
@@ -43,8 +49,8 @@ public class BatchPluginRepository implements PluginRepository, Startable {
 
   @Override
   public void start() {
-    infosByKeys = installer.installRemotes();
-    pluginInstancesByKeys = loader.load(infosByKeys);
+    infosByKeys = Maps.newHashMap(installer.installRemotes());
+    pluginInstancesByKeys = Maps.newHashMap(loader.load(infosByKeys));
 
     // this part is only used by tests
     for (Map.Entry<String, Plugin> entry : installer.installLocals().entrySet()) {
@@ -70,14 +76,16 @@ public class BatchPluginRepository implements PluginRepository, Startable {
 
   @Override
   public PluginInfo getPluginInfo(String key) {
-    // TODO check null result
-    return infosByKeys.get(key);
+    PluginInfo info = infosByKeys.get(key);
+    Preconditions.checkState(info != null, String.format("Plugin [%s] does not exist", key));
+    return info;
   }
 
   @Override
   public Plugin getPluginInstance(String key) {
-    // TODO check null result
-    return pluginInstancesByKeys.get(key);
+    Plugin instance = pluginInstancesByKeys.get(key);
+    Preconditions.checkState(instance != null, String.format("Plugin [%s] does not exist", key));
+    return instance;
   }
 
   @Override
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java
deleted file mode 100644 (file)
index 29f554d..0000000
+++ /dev/null
@@ -1,77 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import org.apache.commons.io.FileUtils;
-import org.sonar.api.BatchComponent;
-import org.sonar.api.utils.ZipUtils;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginUnzipper;
-import org.sonar.core.platform.UnzippedPlugin;
-import org.sonar.home.cache.FileCache;
-
-import java.io.File;
-import java.io.FileOutputStream;
-import java.io.IOException;
-
-public class BatchPluginUnzipper extends PluginUnzipper implements BatchComponent {
-
-  private final FileCache fileCache;
-
-  public BatchPluginUnzipper(FileCache fileCache) {
-    this.fileCache = fileCache;
-  }
-
-  @Override
-  public UnzippedPlugin unzip(PluginInfo info) {
-    try {
-      File dir = unzipFile(info.getFile());
-      return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), dir);
-    } catch (Exception e) {
-      throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getFile().getAbsolutePath()), e);
-    }
-  }
-
-  private File unzipFile(File cachedFile) throws IOException {
-    String filename = cachedFile.getName();
-    File destDir = new File(cachedFile.getParentFile(), filename + "_unzip");
-    File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock");
-    if (!destDir.exists()) {
-      FileOutputStream out = new FileOutputStream(lockFile);
-      try {
-        java.nio.channels.FileLock lock = out.getChannel().lock();
-        try {
-          // Recheck in case of concurrent processes
-          if (!destDir.exists()) {
-            File tempDir = fileCache.createTempDir();
-            ZipUtils.unzip(cachedFile, tempDir, newLibFilter());
-            FileUtils.moveDirectory(tempDir, destDir);
-          }
-        } finally {
-          lock.release();
-        }
-      } finally {
-        out.close();
-        FileUtils.deleteQuietly(lockFile);
-      }
-    }
-    return destDir;
-  }
-}
index 09d217122af77d7dfcc8d8407a6b20a7a302069a..18be773ae8c7e1038d1c4890d5c6d28a77affb8c 100644 (file)
@@ -98,7 +98,7 @@ public class GlobalContainer extends ComponentContainer {
       // plugins
       BatchPluginRepository.class,
       PluginLoader.class,
-      BatchPluginUnzipper.class,
+      BatchPluginExploder.class,
       BatchPluginPredicate.class,
       ExtensionInstaller.class,
 
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java
new file mode 100644 (file)
index 0000000..3d080e3
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.batch.bootstrap;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.home.cache.FileCache;
+import org.sonar.home.cache.FileCacheBuilder;
+
+import java.io.File;
+import java.io.IOException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class BatchPluginExploderTest {
+
+  @ClassRule
+  public static TemporaryFolder temp = new TemporaryFolder();
+
+  File userHome;
+  BatchPluginExploder underTest;
+
+  @Before
+  public void setUp() throws IOException {
+    userHome = temp.newFolder();
+    FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build();
+    underTest = new BatchPluginExploder(fileCache);
+  }
+
+  @Test
+  public void copy_and_extract_libs() throws IOException {
+    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
+    ExplodedPlugin exploded = underTest.explode(PluginInfo.create(fileFromCache));
+
+    assertThat(exploded.getKey()).isEqualTo("checkstyle");
+    assertThat(exploded.getMain()).isFile().exists();
+    assertThat(exploded.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists();
+  }
+
+  @Test
+  public void extract_only_libs() throws IOException {
+    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
+    underTest.explode(PluginInfo.create(fileFromCache));
+
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist();
+    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist();
+  }
+
+  File getFileFromCache(String filename) throws IOException {
+    File src = FileUtils.toFile(BatchPluginExploderTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename));
+    File destFile = new File(new File(userHome, "" + filename.hashCode()), filename);
+    FileUtils.copyFile(src, destFile);
+    return destFile;
+  }
+
+}
index 95d9f18eb4c3a48157e4e64030c2d751cae673ca..571764048458a4098b3487ca7bb0a97b6d06edbd 100644 (file)
@@ -67,7 +67,7 @@ public class BatchPluginPredicateTest {
   }
 
   @Test
-  public void accept_core_plugin_even_if_in_exclusions() {
+  public void accept_core_plugin_even_if_declared_in_exclusions() {
     when(mode.isPreview()).thenReturn(true);
     settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "core,findbugs");
     BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode);
@@ -75,7 +75,7 @@ public class BatchPluginPredicateTest {
   }
 
   @Test
-  public void both_inclusions_and_exclusions() {
+  public void verify_both_inclusions_and_exclusions() {
     when(mode.isPreview()).thenReturn(true);
     settings
       .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
@@ -87,7 +87,7 @@ public class BatchPluginPredicateTest {
   }
 
   @Test
-  public void only_exclusions() {
+  public void test_exclusions_without_any_inclusions() {
     when(mode.isPreview()).thenReturn(true);
     settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
     BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode);
@@ -96,8 +96,13 @@ public class BatchPluginPredicateTest {
     assertThat(predicate.apply("cobertura")).isTrue();
   }
 
+  /**
+   * The properties sonar.dryRun.includePlugins and sonar.dryRun.excludePlugins
+   * are deprecated. They are replaced by sonar.preview.includePlugins and
+   * sonar.preview.excludePlugins.
+   */
   @Test
-  public void deprecated_dry_run_settings() {
+  public void support_deprecated_dry_run_settings() {
     when(mode.isPreview()).thenReturn(true);
     settings
       .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit")
index 7c82edbb64f7a349a58e1f4ad5912d0bfa0b9f6b..cafee2bebf248c77dfb19305111582b01887b469 100644 (file)
  * along with this program; if not, write to the Free Software Foundation,
  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
  */
-///*
-// * SonarQube, open source software quality management tool.
-// * Copyright (C) 2008-2014 SonarSource
-// * mailto:contact AT sonarsource DOT com
-// *
-// * SonarQube 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.
-// *
-// * SonarQube 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  02110-1301, USA.
-// */
-//package org.sonar.batch.bootstrap;
-//
-//import com.google.common.io.Resources;
-//import org.apache.commons.io.FileUtils;
-//import org.junit.After;
-//import org.junit.Before;
-//import org.junit.Rule;
-//import org.junit.Test;
-//import org.junit.rules.TemporaryFolder;
-//import org.sonar.api.CoreProperties;
-//import org.sonar.api.config.Settings;
-//import org.sonar.core.plugins.RemotePlugin;
-//import org.sonar.home.cache.FileCache;
-//import org.sonar.home.cache.FileCacheBuilder;
-//
-//import java.io.File;
-//import java.io.IOException;
-//import java.util.Arrays;
-//
-//import static org.mockito.Mockito.mock;
-//import static org.mockito.Mockito.when;
-//
-//public class BatchPluginRepositoryTest {
-//
-//  @Rule
-//  public TemporaryFolder temp = new TemporaryFolder();
-//
-//  private BatchPluginRepository repository;
-//  private DefaultAnalysisMode mode;
-//  private FileCache cache;
-//  private File userHome;
-//
-//  @Before
-//  public void before() throws IOException {
-//    mode = mock(DefaultAnalysisMode.class);
-//    when(mode.isPreview()).thenReturn(false);
-//    userHome = temp.newFolder();
-//    cache = new FileCacheBuilder().setUserHome(userHome).build();
-//  }
-//
-//  @After
-//  public void tearDown() {
-//    if (repository != null) {
-//      repository.stop();
-//    }
-//  }
-//
-//  @Test
-//  public void shouldLoadPlugin() throws Exception {
-//    RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
-//
-//    DefaultPluginRepository installer = mock(DefaultPluginsRepository.class);
-//    when(installer.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
-//
-//    repository = new BatchPluginRepository(installer, new Settings(), mode, new BatchPluginJarInstaller(cache));
-//
-//    repository.doStart(Arrays.asList(checkstyle));
-//
-//    assertThat(repository.getPlugin("checkstyle")).isNotNull();
-//    assertThat(repository.getMetadata()).hasSize(1);
-//    assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle");
-//    assertThat(repository.getMetadata("checkstyle").getDeployedFiles()).hasSize(4); // plugin + 3 dependencies
-//  }
-//
-//  @Test
-//  public void shouldLoadPluginExtension() throws Exception {
-//    RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
-//    RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false);
-//
-//    DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class);
-//    when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
-//    when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"));
-//
-//    repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache));
-//
-//    repository.doStart(Arrays.asList(checkstyle, checkstyleExt));
-//
-//    assertThat(repository.getPlugin("checkstyle")).isNotNull();
-//    assertThat(repository.getPlugin("checkstyleextensions")).isNotNull();
-//    assertThat(repository.getMetadata()).hasSize(2);
-//    assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle");
-//    assertThat(repository.getMetadata("checkstyleextensions").getVersion()).isEqualTo("0.1-SNAPSHOT");
-//  }
-//
-//  @Test
-//  public void shouldExcludePluginAndItsExtensions() throws Exception {
-//    RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
-//    RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false);
-//
-//    DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class);
-//    when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
-//    when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"));
-//
-//    Settings settings = new Settings();
-//    settings.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle");
-//    repository = new BatchPluginRepository(downloader, settings, mode, new BatchPluginJarInstaller(cache));
-//
-//    repository.doStart(Arrays.asList(checkstyle, checkstyleExt));
-//
-//    assertThat(repository.getMetadata()).isEmpty();
-//  }
-//
-//  private File fileFromCache(String filename) throws Exception {
-//    File file = new File(Resources.getResource("org/sonar/batch/bootstrap/BatchPluginRepositoryTest/" + filename).toURI());
-//    File destDir = new File(userHome, "cache/foomd5");
-//    FileUtils.forceMkdir(destDir);
-//    FileUtils.copyFileToDirectory(file, destDir);
-//    return new File(destDir, filename);
-//  }
-//
-//  @Test
-//  public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() {
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode);
-//    assertThat(filter.accepts("pmd")).isTrue();
-//    assertThat(filter.accepts("buildbreaker")).isTrue();
-//  }
-//
-//  @Test
-//  public void shouldBlackListBuildBreakerInPreviewMode() {
-//    when(mode.isPreview()).thenReturn(true);
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode);
-//    assertThat(filter.accepts("buildbreaker")).isFalse();
-//  }
-//
-//  @Test
-//  public void whiteListShouldTakePrecedenceOverBlackList() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("pmd")).isTrue();
-//  }
-//
-//  @Test
-//  public void corePluginShouldAlwaysBeInWhiteList() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("core")).isTrue();
-//  }
-//
-//  @Test
-//  public void corePluginShouldNeverBeInBlackList() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("core")).isTrue();
-//  }
-//
-//  @Test
-//  public void check_white_list_with_black_list() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("checkstyle")).isTrue();
-//    assertThat(filter.accepts("pmd")).isTrue();
-//    assertThat(filter.accepts("cobertura")).isFalse();
-//  }
-//
-//  @Test
-//  public void check_white_list_when_plugin_is_in_both_list() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "cobertura,checkstyle,pmd,findbugs")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("checkstyle")).isTrue();
-//    assertThat(filter.accepts("pmd")).isTrue();
-//    assertThat(filter.accepts("cobertura")).isTrue();
-//  }
-//
-//  @Test
-//  public void check_black_list_if_no_white_list() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("checkstyle")).isFalse();
-//    assertThat(filter.accepts("pmd")).isFalse();
-//    assertThat(filter.accepts("cobertura")).isTrue();
-//  }
-//
-//  @Test
-//  public void should_concatenate_preview_filters() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "cockpit")
-//      .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "views")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd");
-//    when(mode.isPreview()).thenReturn(true);
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.whites).containsOnly("cockpit");
-//    assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd");
-//  }
-//
-//  @Test
-//  public void should_concatenate_deprecated_dry_run_filters() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit")
-//      .setProperty(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, "views")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd");
-//    when(mode.isPreview()).thenReturn(true);
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.whites).containsOnly("cockpit");
-//    assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd");
-//  }
-//
-//  @Test
-//  public void inclusions_and_exclusions_should_be_trimmed() {
-//    Settings settings = new Settings()
-//      .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs")
-//      .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura, pmd");
-//    BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
-//    assertThat(filter.accepts("pmd")).isTrue();
-//  }
-//
-//}
+package org.sonar.batch.bootstrap;
+
+import com.google.common.collect.ImmutableMap;
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginLoader;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyCollectionOf;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class BatchPluginRepositoryTest {
+
+  PluginInstaller installer = mock(PluginInstaller.class);
+  PluginLoader loader = mock(PluginLoader.class);
+  BatchPluginRepository underTest = new BatchPluginRepository(installer, loader);
+
+  @Test
+  public void install_and_load_plugins() throws Exception {
+    PluginInfo info = new PluginInfo("squid");
+    ImmutableMap<String, PluginInfo> infos = ImmutableMap.of("squid", info);
+    Plugin instance = mock(Plugin.class);
+    when(loader.load(infos)).thenReturn(ImmutableMap.of("squid", instance));
+    when(installer.installRemotes()).thenReturn(infos);
+
+    underTest.start();
+
+    assertThat(underTest.getPluginInfos()).containsOnly(info);
+    assertThat(underTest.getPluginInfo("squid")).isSameAs(info);
+    assertThat(underTest.getPluginInstance("squid")).isSameAs(instance);
+
+    underTest.stop();
+    verify(loader).unload(anyCollectionOf(Plugin.class));
+  }
+
+  @Test
+  public void fail_if_requesting_missing_plugin() throws Exception {
+    underTest.start();
+
+    try {
+      underTest.getPluginInfo("unknown");
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Plugin [unknown] does not exist");
+    }
+    try {
+      underTest.getPluginInstance("unknown");
+      fail();
+    } catch (IllegalStateException e) {
+      assertThat(e).hasMessage("Plugin [unknown] does not exist");
+    }
+  }
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java
deleted file mode 100644 (file)
index 06a2514..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.batch.bootstrap;
-
-import org.apache.commons.io.FileUtils;
-import org.junit.Before;
-import org.junit.ClassRule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.UnzippedPlugin;
-import org.sonar.home.cache.FileCache;
-import org.sonar.home.cache.FileCacheBuilder;
-
-import java.io.File;
-import java.io.IOException;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class BatchPluginUnzipperTest {
-
-  @ClassRule
-  public static TemporaryFolder temp = new TemporaryFolder();
-
-  File userHome;
-  BatchPluginUnzipper underTest;
-
-  @Before
-  public void setUp() throws IOException {
-    userHome = temp.newFolder();
-    FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build();
-    underTest = new BatchPluginUnzipper(fileCache);
-  }
-
-  @Test
-  public void copy_and_extract_libs() throws IOException {
-    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
-    UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache));
-
-    assertThat(unzipped.getKey()).isEqualTo("checkstyle");
-    assertThat(unzipped.getMain()).isFile().exists();
-    assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists();
-  }
-
-  @Test
-  public void extract_only_libs() throws IOException {
-    File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
-    underTest.unzip(PluginInfo.create(fileFromCache));
-
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist();
-    assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist();
-  }
-
-  File getFileFromCache(String filename) throws IOException {
-    File src = FileUtils.toFile(BatchPluginUnzipperTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename));
-    File destFile = new File(new File(userHome, "" + filename.hashCode()), filename);
-    FileUtils.copyFile(src, destFile);
-    return destFile;
-  }
-
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java b/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java
new file mode 100644 (file)
index 0000000..3ebe5cc
--- /dev/null
@@ -0,0 +1,97 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.core.platform;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import java.util.Collection;
+import javax.annotation.Nullable;
+import org.sonar.classloader.Mask;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Information about the classloader to be created for a set of plugins.
+ */
+class ClassloaderDef {
+
+  private final String basePluginKey;
+  private final Map<String, String> mainClassesByPluginKey = new HashMap<>();
+  private final List<File> files = new ArrayList<>();
+  private final Mask mask = new Mask();
+  private boolean selfFirstStrategy = false;
+  private ClassLoader classloader = null;
+
+  ClassloaderDef(String basePluginKey) {
+    Preconditions.checkNotNull(basePluginKey);
+    this.basePluginKey = basePluginKey;
+  }
+
+  String getBasePluginKey() {
+    return basePluginKey;
+  }
+
+  Map<String, String> getMainClassesByPluginKey() {
+    return mainClassesByPluginKey;
+  }
+
+  List<File> getFiles() {
+    return files;
+  }
+
+  Mask getMask() {
+    return mask;
+  }
+
+  boolean isSelfFirstStrategy() {
+    return selfFirstStrategy;
+  }
+
+  void setSelfFirstStrategy(boolean selfFirstStrategy) {
+    this.selfFirstStrategy = selfFirstStrategy;
+  }
+
+  /**
+   * Returns the newly created classloader. Throws an exception
+   * if null, for example because called before {@link #setBuiltClassloader(ClassLoader)}
+   */
+  ClassLoader getBuiltClassloader() {
+    Preconditions.checkState(classloader != null);
+    return classloader;
+  }
+
+  void setBuiltClassloader(ClassLoader c) {
+    this.classloader = c;
+  }
+
+  void addFiles(Collection<File> c) {
+    this.files.addAll(c);
+  }
+
+  void addMainClass(String pluginKey, @Nullable String mainClass) {
+    if (!Strings.isNullOrEmpty(mainClass)) {
+      mainClassesByPluginKey.put(pluginKey, mainClass);
+    }
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
new file mode 100644 (file)
index 0000000..a5a9a50
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.core.platform;
+
+import java.io.File;
+import java.util.Collection;
+
+public class ExplodedPlugin {
+
+  private final String key;
+  private final File main;
+  private final Collection<File> libs;
+
+  public ExplodedPlugin(String key, File main, Collection<File> libs) {
+    this.key = key;
+    this.main = main;
+    this.libs = libs;
+  }
+
+  public String getKey() {
+    return key;
+  }
+
+  public File getMain() {
+    return main;
+  }
+
+  public Collection<File> getLibs() {
+    return libs;
+  }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java
new file mode 100644 (file)
index 0000000..50681f1
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.core.platform;
+
+import java.io.File;
+import java.util.Collection;
+import java.util.Collections;
+import org.sonar.api.utils.ZipUtils;
+
+import java.util.zip.ZipEntry;
+
+import static org.apache.commons.io.FileUtils.listFiles;
+
+public abstract class PluginExploder {
+
+  protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib";
+
+  public abstract ExplodedPlugin explode(PluginInfo info);
+
+  protected ZipUtils.ZipEntryFilter newLibFilter() {
+    return ZipLibFilter.INSTANCE;
+  }
+
+  protected ExplodedPlugin explodeFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) {
+    File libDir = new File(unzippedDir, PluginExploder.LIB_RELATIVE_PATH_IN_JAR);
+    Collection<File> libs;
+    if (libDir.isDirectory() && libDir.exists()) {
+      libs = listFiles(libDir, null, false);
+    } else {
+      libs = Collections.emptyList();
+    }
+    return new ExplodedPlugin(pluginKey, jarFile, libs);
+  }
+
+  private enum ZipLibFilter implements ZipUtils.ZipEntryFilter {
+    INSTANCE;
+
+    @Override
+    public boolean accept(ZipEntry entry) {
+      return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR);
+    }
+  }
+}
index e5509bbc7e7df1bda4918b2111b1f9e00b722c8a..317ced0ba21acce59cb3fa1e615b34f365a6cf13 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.core.platform;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.base.Preconditions;
 import org.apache.commons.lang.StringUtils;
 import org.sonar.updatecenter.common.PluginManifest;
 import org.sonar.updatecenter.common.Version;
@@ -31,12 +33,18 @@ import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 
 import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.regex.Pattern;
 
 public class PluginInfo implements Comparable<PluginInfo> {
 
+  private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
+
   public static class RequiredPlugin {
+
+    private static final Pattern PARSER = Pattern.compile("\\w+:.+");
+
     private final String key;
     private final Version minimalVersion;
 
@@ -54,44 +62,94 @@ public class PluginInfo implements Comparable<PluginInfo> {
     }
 
     public static RequiredPlugin parse(String s) {
-      if (!s.matches("\\w+:.+")) {
+      if (!PARSER.matcher(s).matches()) {
         throw new IllegalArgumentException("Manifest field does not have correct format: " + s);
       }
       String[] fields = StringUtils.split(s, ':');
       return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());
     }
+
+    @Override
+    public boolean equals(Object o) {
+      if (this == o) {
+        return true;
+      }
+      if (o == null || getClass() != o.getClass()) {
+        return false;
+      }
+      RequiredPlugin that = (RequiredPlugin) o;
+      return key.equals(that.key);
+    }
+
+    @Override
+    public int hashCode() {
+      return key.hashCode();
+    }
   }
 
-  private File file;
   private String key;
   private String name;
+
+  @CheckForNull
+  private File jarFile;
+
+  @CheckForNull
+  private String mainClass;
+
+  @CheckForNull
   private Version version;
+
+  @CheckForNull
   private Version minimalSqVersion;
-  private String mainClass;
+
+  @CheckForNull
   private String description;
+
+  @CheckForNull
   private String organizationName;
+
+  @CheckForNull
   private String organizationUrl;
+
+  @CheckForNull
   private String license;
+
+  @CheckForNull
   private String homepageUrl;
+
+  @CheckForNull
   private String issueTrackerUrl;
+
   private boolean useChildFirstClassLoader;
+
+  @CheckForNull
   private String basePlugin;
-  private boolean core;
+
+  private boolean core = false;
+
+  @CheckForNull
   private String implementationBuild;
-  private final List<RequiredPlugin> requiredPlugins = new ArrayList<>();
 
-  public PluginInfo() {
-  }
+  private final Set<RequiredPlugin> requiredPlugins = new HashSet<>();
 
-  /**
-   * For tests only
-   */
   public PluginInfo(String key) {
     this.key = key;
+    this.name = key;
+  }
+
+  public PluginInfo setJarFile(@Nullable File f) {
+    this.jarFile = f;
+    return this;
   }
 
-  public File getFile() {
-    return file;
+  @CheckForNull
+  public File getJarFile2() {
+    return jarFile;
+  }
+
+  public File getNonNullJarFile() {
+    Preconditions.checkNotNull(jarFile);
+    return jarFile;
   }
 
   public String getKey() {
@@ -112,6 +170,7 @@ public class PluginInfo implements Comparable<PluginInfo> {
     return minimalSqVersion;
   }
 
+  @CheckForNull
   public String getMainClass() {
     return mainClass;
   }
@@ -164,37 +223,15 @@ public class PluginInfo implements Comparable<PluginInfo> {
     return implementationBuild;
   }
 
-  public List<RequiredPlugin> getRequiredPlugins() {
+  public Set<RequiredPlugin> getRequiredPlugins() {
     return requiredPlugins;
   }
 
-  /**
-   * Required
-   */
-  public PluginInfo setFile(File file) {
-    this.file = file;
+  public PluginInfo setName(@Nullable String name) {
+    this.name = Objects.firstNonNull(name, this.key);
     return this;
   }
 
-  /**
-   * Required
-   */
-  public PluginInfo setKey(String key) {
-    this.key = key;
-    return this;
-  }
-
-  /**
-   * Required
-   */
-  public PluginInfo setName(String name) {
-    this.name = name;
-    return this;
-  }
-
-  /**
-   * Required
-   */
   public PluginInfo setVersion(Version version) {
     this.version = version;
     return this;
@@ -286,7 +323,7 @@ public class PluginInfo implements Comparable<PluginInfo> {
 
   @Override
   public String toString() {
-    return String.format("[%s]", Joiner.on(" / ").skipNulls().join(key, version, implementationBuild));
+    return String.format("[%s]", SLASH_JOINER.join(key, version, implementationBuild));
   }
 
   @Override
@@ -310,11 +347,9 @@ public class PluginInfo implements Comparable<PluginInfo> {
 
   @VisibleForTesting
   static PluginInfo create(File jarFile, PluginManifest manifest) {
-    PluginInfo info = new PluginInfo();
+    PluginInfo info = new PluginInfo(manifest.getKey());
 
-    // required fields
-    info.setKey(manifest.getKey());
-    info.setFile(jarFile);
+    info.setJarFile(jarFile);
     info.setName(manifest.getName());
     info.setMainClass(manifest.getMainClass());
     info.setVersion(Version.create(manifest.getVersion()));
@@ -342,12 +377,16 @@ public class PluginInfo implements Comparable<PluginInfo> {
     return info;
   }
 
-  public enum JarToPluginInfo implements Function<File, PluginInfo> {
+  private enum JarToPluginInfo implements Function<File, PluginInfo> {
     INSTANCE;
 
     @Override
     public PluginInfo apply(@Nonnull File jarFile) {
       return create(jarFile);
     }
-  };
+  }
+
+  public static Function<File, PluginInfo>  jarToPluginInfo() {
+    return JarToPluginInfo.INSTANCE;
+  }
 }
index 336c4904bcf4536093f9a9abffe410fcbaf1d630..b758f12d423c694bd2610593e44e3672380ac77d 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.core.platform;
 
+import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Strings;
 import org.apache.commons.lang.SystemUtils;
 import org.sonar.api.BatchComponent;
@@ -27,24 +28,26 @@ import org.sonar.api.ServerComponent;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.classloader.ClassloaderBuilder;
 import org.sonar.classloader.ClassloaderBuilder.LoadingOrder;
-import org.sonar.classloader.Mask;
 
 import java.io.Closeable;
 import java.io.File;
 import java.net.MalformedURLException;
 import java.net.URL;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
-import java.util.List;
 import java.util.Map;
 
+import static java.util.Arrays.asList;
 import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST;
 
 /**
  * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
  * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current
- * environment (minimal sonarqube version, compatibility between plugins, ...).
+ * environment (minimal sonarqube version, compatibility between plugins, ...):
+ * <ul>
+ *   <li>server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)</li>
+ *   <li>batch loads only the plugins deployed on server</li>
+ * </ul>
  * <p/>
  * Standard plugins have their own isolated classloader. Some others can extend a "base" plugin.
  * In this case they share the same classloader then the base plugin.
@@ -55,30 +58,14 @@ public class PluginLoader implements BatchComponent, ServerComponent {
 
   private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins/", "com/sonar/plugins/", "com/sonarsource/plugins/"};
 
-  /**
-   * Information about the classloader to be created for a set of plugins.
-   */
-  static class ClassloaderDef {
-    final String basePluginKey;
-    final Map<String, String> mainClassesByPluginKey = new HashMap<>();
-    final List<File> files = new ArrayList<>();
-    final Mask mask = new Mask();
-    boolean selfFirstStrategy = false;
-    ClassLoader classloader = null;
-
-    public ClassloaderDef(String basePluginKey) {
-      this.basePluginKey = basePluginKey;
-    }
-  }
+  private final PluginExploder exploder;
 
-  private final PluginUnzipper unzipper;
-
-  public PluginLoader(PluginUnzipper unzipper) {
-    this.unzipper = unzipper;
+  public PluginLoader(PluginExploder exploder) {
+    this.exploder = exploder;
   }
 
   public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
-    Collection<ClassloaderDef> defs = defineClassloaders(infoByKeys).values();
+    Collection<ClassloaderDef> defs = defineClassloaders(infoByKeys);
     buildClassloaders(defs);
     return instantiatePluginInstances(defs);
   }
@@ -87,7 +74,8 @@ public class PluginLoader implements BatchComponent, ServerComponent {
    * Step 1 - define the different classloaders to be created. Number of classloaders can be
    * different than number of plugins.
    */
-  Map<String, ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
+  @VisibleForTesting
+  Collection<ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
     Map<String, ClassloaderDef> classloadersByBasePlugin = new HashMap<>();
 
     for (PluginInfo info : infoByKeys.values()) {
@@ -97,39 +85,44 @@ public class PluginLoader implements BatchComponent, ServerComponent {
         def = new ClassloaderDef(baseKey);
         classloadersByBasePlugin.put(baseKey, def);
       }
-      UnzippedPlugin unzippedPlugin = unzipper.unzip(info);
-      def.files.add(unzippedPlugin.getMain());
-      def.files.addAll(unzippedPlugin.getLibs());
-      def.mainClassesByPluginKey.put(info.getKey(), info.getMainClass());
+      ExplodedPlugin explodedPlugin = exploder.explode(info);
+      def.addFiles(asList(explodedPlugin.getMain()));
+      def.addFiles(explodedPlugin.getLibs());
+      def.addMainClass(info.getKey(), info.getMainClass());
+
       for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) {
-        def.mask.addInclusion(defaultSharedResource + info.getKey() + "/");
+        def.getMask().addInclusion(defaultSharedResource + info.getKey() + "/");
       }
       if (Strings.isNullOrEmpty(info.getBasePlugin())) {
         // The plugins that extend other plugins can only add some files to classloader.
         // They can't change ordering strategy.
-        def.selfFirstStrategy = info.isUseChildFirstClassLoader();
+        def.setSelfFirstStrategy(info.isUseChildFirstClassLoader());
       }
     }
-    return classloadersByBasePlugin;
+    return classloadersByBasePlugin.values();
   }
 
   /**
    * Step 2 - create classloaders with appropriate constituents and metadata
    */
-  void buildClassloaders(Collection<ClassloaderDef> defs) {
+  private void buildClassloaders(Collection<ClassloaderDef> defs) {
     ClassloaderBuilder builder = new ClassloaderBuilder();
     for (ClassloaderDef def : defs) {
       builder
-        .newClassloader(def.basePluginKey, getClass().getClassLoader())
-        .setExportMask(def.basePluginKey, def.mask)
-        .setLoadingOrder(def.basePluginKey, def.selfFirstStrategy ? SELF_FIRST : LoadingOrder.PARENT_FIRST);
-      for (File file : def.files) {
-        builder.addURL(def.basePluginKey, fileToUrl(file));
+        .newClassloader(def.getBasePluginKey(), getClass().getClassLoader())
+        .setExportMask(def.getBasePluginKey(), def.getMask())
+        .setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : LoadingOrder.PARENT_FIRST);
+      for (File file : def.getFiles()) {
+        builder.addURL(def.getBasePluginKey(), fileToUrl(file));
       }
     }
     Map<String, ClassLoader> classloadersByBasePluginKey = builder.build();
     for (ClassloaderDef def : defs) {
-      def.classloader = classloadersByBasePluginKey.get(def.basePluginKey);
+      ClassLoader builtClassloader = classloadersByBasePluginKey.get(def.getBasePluginKey());
+      if (builtClassloader == null) {
+        throw new IllegalStateException(String.format("Fail to create classloader for plugin [%s]", def.getBasePluginKey()));
+      }
+      def.setBuiltClassloader(builtClassloader);
     }
   }
 
@@ -139,16 +132,16 @@ public class PluginLoader implements BatchComponent, ServerComponent {
    * @return the instances grouped by plugin key
    * @throws IllegalStateException if at least one plugin can't be correctly loaded
    */
-  Map<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) {
+  private Map<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) {
     // instantiate plugins
     Map<String, Plugin> instancesByPluginKey = new HashMap<>();
     for (ClassloaderDef def : defs) {
       // the same classloader can be used by multiple plugins
-      for (Map.Entry<String, String> entry : def.mainClassesByPluginKey.entrySet()) {
+      for (Map.Entry<String, String> entry : def.getMainClassesByPluginKey().entrySet()) {
         String pluginKey = entry.getKey();
         String mainClass = entry.getValue();
         try {
-          instancesByPluginKey.put(pluginKey, (Plugin) def.classloader.loadClass(mainClass).newInstance());
+          instancesByPluginKey.put(pluginKey, (Plugin) def.getBuiltClassloader().loadClass(mainClass).newInstance());
         } catch (UnsupportedClassVersionError e) {
           throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s",
             pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e);
@@ -189,7 +182,7 @@ public class PluginLoader implements BatchComponent, ServerComponent {
     return base;
   }
 
-  private URL fileToUrl(File file) {
+  private static URL fileToUrl(File file) {
     try {
       return file.toURI().toURL();
     } catch (MalformedURLException e) {
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java
deleted file mode 100644 (file)
index 5ce1ca0..0000000
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.core.platform;
-
-import org.sonar.api.utils.ZipUtils;
-
-import java.util.zip.ZipEntry;
-
-public abstract class PluginUnzipper {
-
-  protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib";
-
-  public abstract UnzippedPlugin unzip(PluginInfo info);
-
-  protected ZipUtils.ZipEntryFilter newLibFilter() {
-    return new ZipUtils.ZipEntryFilter() {
-      @Override
-      public boolean accept(ZipEntry entry) {
-        return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR);
-      }
-    };
-  }
-}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java
deleted file mode 100644 (file)
index 3c73b8d..0000000
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.core.platform;
-
-import java.io.File;
-import java.util.Collection;
-import java.util.Collections;
-
-import static org.apache.commons.io.FileUtils.listFiles;
-
-public class UnzippedPlugin {
-
-  private final String key;
-  private final File main;
-  private final Collection<File> libs;
-
-  public UnzippedPlugin(String key, File main, Collection<File> libs) {
-    this.key = key;
-    this.main = main;
-    this.libs = libs;
-  }
-
-  public String getKey() {
-    return key;
-  }
-
-  public File getMain() {
-    return main;
-  }
-
-  public Collection<File> getLibs() {
-    return libs;
-  }
-
-  public static UnzippedPlugin createFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) {
-    File libDir = new File(unzippedDir, PluginUnzipper.LIB_RELATIVE_PATH_IN_JAR);
-    Collection<File> libs;
-    if (libDir.isDirectory() && libDir.exists()) {
-      libs = listFiles(libDir, null, false);
-    } else {
-      libs = Collections.emptyList();
-    }
-    return new UnzippedPlugin(pluginKey, jarFile, libs);
-  }
-}
index 6fecfe0ae91897a1f01defb82895ab34419c7a90..559faeb5a1a88630e3a6b04f12eb8f18fe6d70fc 100644 (file)
@@ -35,9 +35,9 @@ public class RemotePlugin {
     this.core = core;
   }
 
-  public static RemotePlugin create(PluginInfo metadata) {
-    RemotePlugin result = new RemotePlugin(metadata.getKey(), metadata.isCore());
-    result.setFile(metadata.getFile());
+  public static RemotePlugin create(PluginInfo pluginInfo) {
+    RemotePlugin result = new RemotePlugin(pluginInfo.getKey(), pluginInfo.isCore());
+    result.setFile(pluginInfo.getNonNullJarFile());
     return result;
   }
 
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java
new file mode 100644 (file)
index 0000000..85906b1
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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  02110-1301, USA.
+ */
+package org.sonar.core.platform;
+
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.ZipUtils;
+
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PluginExploderTest {
+
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void unzip_plugin_with_libs() throws Exception {
+    final File jarFile = getFile("sonar-checkstyle-plugin-2.8.jar");
+    final File toDir = temp.newFolder();
+    PluginInfo pluginInfo = new PluginInfo("checkstyle").setJarFile(jarFile);
+
+    PluginExploder exploder = new PluginExploder() {
+      @Override
+      public ExplodedPlugin explode(PluginInfo info) {
+        try {
+          ZipUtils.unzip(jarFile, toDir, newLibFilter());
+          return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), toDir);
+        } catch (Exception e) {
+          throw new IllegalStateException(e);
+        }
+      }
+    };
+    ExplodedPlugin exploded = exploder.explode(pluginInfo);
+    assertThat(exploded.getKey()).isEqualTo("checkstyle");
+    assertThat(exploded.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
+    assertThat(exploded.getMain()).isSameAs(jarFile);
+  }
+
+  @Test
+  public void unzip_plugin_without_libs() throws Exception {
+    File jarFile = temp.newFile();
+    final File toDir = temp.newFolder();
+    PluginInfo pluginInfo = new PluginInfo("foo").setJarFile(jarFile);
+
+    PluginExploder exploder = new PluginExploder() {
+      @Override
+      public ExplodedPlugin explode(PluginInfo info) {
+        return explodeFromUnzippedDir("foo", info.getNonNullJarFile(), toDir);
+      }
+    };
+    ExplodedPlugin exploded = exploder.explode(pluginInfo);
+    assertThat(exploded.getKey()).isEqualTo("foo");
+    assertThat(exploded.getLibs()).isEmpty();
+    assertThat(exploded.getMain()).isSameAs(jarFile);
+  }
+
+  private File getFile(String filename) {
+    return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename));
+  }
+}
index 2b702748c4bb77129bd451173be4a07cdc8a58d3..198f9e0c9f1d2f533ef1732f8b2146981be6d3be 100644 (file)
@@ -23,17 +23,16 @@ import org.apache.commons.io.FileUtils;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.api.utils.ManifestUtils;
 import org.sonar.updatecenter.common.PluginManifest;
 import org.sonar.updatecenter.common.Version;
 
 import javax.annotation.Nullable;
 
 import java.io.File;
+import java.io.IOException;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.jar.Manifest;
 
 import static com.google.common.collect.Ordering.natural;
 import static org.assertj.core.api.Assertions.assertThat;
@@ -59,21 +58,23 @@ public class PluginInfoTest {
   }
 
   @Test
-  public void test_compare() {
-    PluginInfo java1 = new PluginInfo("java").setName("Java").setVersion(Version.create("1.0"));
-    PluginInfo java2 = new PluginInfo("java").setName("Java").setVersion(Version.create("2.0"));
-    PluginInfo cobol = new PluginInfo("cobol").setName("Cobol").setVersion(Version.create("1.0"));
-    List<PluginInfo> plugins = Arrays.asList(java1, java2, cobol);
+  public void test_comparison() {
+    PluginInfo java1 = new PluginInfo("java").setVersion(Version.create("1.0"));
+    PluginInfo java2 = new PluginInfo("java").setVersion(Version.create("2.0"));
+    PluginInfo cobol = new PluginInfo("cobol").setVersion(Version.create("1.0"));
+    PluginInfo noVersion = new PluginInfo("noVersion");
+    List<PluginInfo> plugins = Arrays.asList(java1, java2, cobol, noVersion);
     Collections.shuffle(plugins);
 
     List<PluginInfo> ordered = natural().sortedCopy(plugins);
     assertThat(ordered.get(0)).isSameAs(cobol);
     assertThat(ordered.get(1)).isSameAs(java1);
     assertThat(ordered.get(2)).isSameAs(java2);
+    assertThat(ordered.get(3)).isSameAs(noVersion);
   }
 
   @Test
-  public void test_compatibility_with_sq_version() {
+  public void test_compatibility_with_sq_version() throws IOException {
     assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1")).isTrue();
     assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1.0")).isTrue();
     assertThat(withMinSqVersion("1.0").isCompatibleWith("1.0.0")).isTrue();
@@ -117,10 +118,9 @@ public class PluginInfoTest {
     assertThat(pluginInfo.getKey()).isEqualTo("java");
     assertThat(pluginInfo.getName()).isEqualTo("Java");
     assertThat(pluginInfo.getVersion().getName()).isEqualTo("1.0");
-    assertThat(pluginInfo.getFile()).isSameAs(jarFile);
+    assertThat(pluginInfo.getJarFile2()).isSameAs(jarFile);
     assertThat(pluginInfo.getMainClass()).isEqualTo("org.foo.FooPlugin");
-
-    // optional fields
+    assertThat(pluginInfo.isCore()).isFalse();
     assertThat(pluginInfo.getBasePlugin()).isNull();
     assertThat(pluginInfo.getDescription()).isNull();
     assertThat(pluginInfo.getHomepageUrl()).isNull();
@@ -152,7 +152,7 @@ public class PluginInfoTest {
     manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"});
 
     File jarFile = temp.newFile();
-    PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest);
+    PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest).setCore(true);
 
     assertThat(pluginInfo.getBasePlugin()).isEqualTo("findbugs");
     assertThat(pluginInfo.getDescription()).isEqualTo("the desc");
@@ -164,6 +164,7 @@ public class PluginInfoTest {
     assertThat(pluginInfo.getOrganizationUrl()).isEqualTo("http://sonarsource.com");
     assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo("4.5.1");
     assertThat(pluginInfo.getRequiredPlugins()).extracting("key").containsExactly("java", "pmd");
+    assertThat(pluginInfo.isCore()).isTrue();
   }
 
   @Test
@@ -177,23 +178,14 @@ public class PluginInfoTest {
 
   @Test
   public void test_toString() throws Exception {
-    PluginInfo pluginInfo = new PluginInfo().setKey("java").setVersion(Version.create("1.1"));
+    PluginInfo pluginInfo = new PluginInfo("java").setVersion(Version.create("1.1"));
     assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1]");
 
     pluginInfo.setImplementationBuild("SHA1");
     assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1 / SHA1]");
   }
 
-  @Test
-  public void isCore() throws Exception {
-    PluginInfo pluginInfo = new PluginInfo();
-    assertThat(pluginInfo.isCore()).isFalse();
-
-    pluginInfo.setCore(true);
-    assertThat(pluginInfo.isCore()).isTrue();
-  }
-
-  static PluginInfo withMinSqVersion(@Nullable String version) {
+  PluginInfo withMinSqVersion(@Nullable String version) throws IOException {
     PluginInfo pluginInfo = new PluginInfo("foo");
     if (version != null) {
       pluginInfo.setMinimalSqVersion(Version.create(version));
index 7cb984b1bdabce6a10b770ba4cd94c6388a0b982..c32fc2f711a42d87f576b314ec5c55fa7ef76d71 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.utils.ZipUtils;
 
 import java.io.File;
 import java.io.IOException;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Map;
 
@@ -42,11 +43,11 @@ public class PluginLoaderTest {
   public TemporaryFolder temp = new TemporaryFolder();
 
   @Test
-  public void complete_test() throws Exception {
+  public void load_and_unload_plugins() throws Exception {
     File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar"));
     PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar);
 
-    PluginLoader loader = new PluginLoader(new TempPluginUnzipper());
+    PluginLoader loader = new PluginLoader(new TempPluginExploder());
     Map<String, Plugin> instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo));
 
     assertThat(instances).containsOnlyKeys("checkstyle");
@@ -54,74 +55,90 @@ public class PluginLoaderTest {
     assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin");
 
     loader.unload(instances.values());
-    // should test that classloaders are closed
+    // TODO test that classloaders are closed. Two strategies:
+    //
   }
 
   @Test
-  public void define_plugin_classloader__nominal() throws Exception {
+  public void define_classloader() throws Exception {
+    File jarFile = temp.newFile();
     PluginInfo info = new PluginInfo("foo")
-      .setName("Foo")
+      .setJarFile(jarFile)
       .setMainClass("org.foo.FooPlugin");
-    File jarFile = temp.newFile();
-    info.setFile(jarFile);
 
-    PluginLoader loader = new PluginLoader(new BasicPluginUnzipper());
-    Map<String, PluginLoader.ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info));
+    PluginLoader loader = new PluginLoader(new FakePluginExploder());
+    Collection<ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info));
 
-    assertThat(defs).containsOnlyKeys("foo");
-    PluginLoader.ClassloaderDef def = defs.get("foo");
-    assertThat(def.basePluginKey).isEqualTo("foo");
-    assertThat(def.selfFirstStrategy).isFalse();
-    assertThat(def.files).containsOnly(jarFile);
-    assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
+    assertThat(defs).hasSize(1);
+    ClassloaderDef def = defs.iterator().next();
+    assertThat(def.getBasePluginKey()).isEqualTo("foo");
+    assertThat(def.isSelfFirstStrategy()).isFalse();
+    assertThat(def.getFiles()).containsOnly(jarFile);
+    assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
     // TODO test mask - require change in sonar-classloader
   }
 
+  /**
+   * A plugin can be extended by other plugins. In this case they share the same classloader.
+   * The first plugin is named "base plugin".
+   */
   @Test
-  public void define_plugin_classloader__extend_base_plugin() throws Exception {
-    File baseJarFile = temp.newFile(), extensionJarFile = temp.newFile();
+  public void define_same_classloader_for_multiple_plugins() throws Exception {
+    File baseJarFile = temp.newFile(), extensionJar1 = temp.newFile(), extensionJar2 = temp.newFile();
     PluginInfo base = new PluginInfo("foo")
-      .setName("Foo")
+      .setJarFile(baseJarFile)
       .setMainClass("org.foo.FooPlugin")
-      .setFile(baseJarFile);
-    PluginInfo extension = new PluginInfo("fooContrib")
-      .setName("Foo Contrib")
-      .setMainClass("org.foo.ContribPlugin")
-      .setFile(extensionJarFile)
+      .setUseChildFirstClassLoader(false);
+
+    PluginInfo extension1 = new PluginInfo("fooExtension1")
+      .setJarFile(extensionJar1)
+      .setMainClass("org.foo.Extension1Plugin")
+      .setBasePlugin("foo");
+
+    // This extension tries to change the classloader-ordering strategy of base plugin
+    // (see setUseChildFirstClassLoader(true)).
+    // That is not allowed and should be ignored -> strategy is still the one
+    // defined on base plugin (parent-first in this example)
+    PluginInfo extension2 = new PluginInfo("fooExtension2")
+      .setJarFile(extensionJar2)
+      .setMainClass("org.foo.Extension2Plugin")
       .setBasePlugin("foo")
-
-      // not a base plugin, can't override base metadata -> will be ignored
       .setUseChildFirstClassLoader(true);
 
-    PluginLoader loader = new PluginLoader(new BasicPluginUnzipper());
-    Map<String, PluginLoader.ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", base, "fooContrib", extension));
+    PluginLoader loader = new PluginLoader(new FakePluginExploder());
+
+    Collection<ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of(
+      base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2));
 
-    assertThat(defs).containsOnlyKeys("foo");
-    PluginLoader.ClassloaderDef def = defs.get("foo");
-    assertThat(def.basePluginKey).isEqualTo("foo");
-    assertThat(def.selfFirstStrategy).isFalse();
-    assertThat(def.files).containsOnly(baseJarFile, extensionJarFile);
-    assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"), entry("fooContrib", "org.foo.ContribPlugin"));
+    assertThat(defs).hasSize(1);
+    ClassloaderDef def = defs.iterator().next();
+    assertThat(def.getBasePluginKey()).isEqualTo("foo");
+    assertThat(def.isSelfFirstStrategy()).isFalse();
+    assertThat(def.getFiles()).containsOnly(baseJarFile, extensionJar1, extensionJar2);
+    assertThat(def.getMainClassesByPluginKey()).containsOnly(
+      entry("foo", "org.foo.FooPlugin"),
+      entry("fooExtension1", "org.foo.Extension1Plugin"),
+      entry("fooExtension2", "org.foo.Extension2Plugin"));
     // TODO test mask - require change in sonar-classloader
   }
 
   /**
-   * Does not unzip jar file.
+   * Does not unzip jar file. It directly returns the JAR file defined on PluginInfo.
    */
-  private class BasicPluginUnzipper extends PluginUnzipper {
+  private static class FakePluginExploder extends PluginExploder {
     @Override
-    public UnzippedPlugin unzip(PluginInfo info) {
-      return new UnzippedPlugin(info.getKey(), info.getFile(), Collections.<File>emptyList());
+    public ExplodedPlugin explode(PluginInfo info) {
+      return new ExplodedPlugin(info.getKey(), info.getNonNullJarFile(), Collections.<File>emptyList());
     }
   }
 
-  private class TempPluginUnzipper extends PluginUnzipper {
+  private class TempPluginExploder extends PluginExploder {
     @Override
-    public UnzippedPlugin unzip(PluginInfo info) {
+    public ExplodedPlugin explode(PluginInfo info) {
       try {
         File tempDir = temp.newFolder();
-        ZipUtils.unzip(info.getFile(), tempDir, newLibFilter());
-        return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), tempDir);
+        ZipUtils.unzip(info.getNonNullJarFile(), tempDir, newLibFilter());
+        return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), tempDir);
 
       } catch (IOException e) {
         throw new IllegalStateException(e);
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java
deleted file mode 100644 (file)
index cbdd9c2..0000000
+++ /dev/null
@@ -1,81 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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  02110-1301, USA.
- */
-package org.sonar.core.platform;
-
-import org.apache.commons.io.FileUtils;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.utils.ZipUtils;
-
-import java.io.File;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class PluginUnzipperTest {
-
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  @Test
-  public void unzip_plugin_with_libs() throws Exception {
-    final File jarFile = getFile("sonar-checkstyle-plugin-2.8.jar");
-    final File toDir = temp.newFolder();
-    PluginInfo pluginInfo = new PluginInfo().setKey("checkstyle").setFile(jarFile);
-
-    PluginUnzipper unzipper = new PluginUnzipper() {
-      @Override
-      public UnzippedPlugin unzip(PluginInfo info) {
-        try {
-          ZipUtils.unzip(jarFile, toDir, newLibFilter());
-          return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), toDir);
-        } catch (Exception e) {
-          throw new IllegalStateException(e);
-        }
-      }
-    };
-    UnzippedPlugin unzipped = unzipper.unzip(pluginInfo);
-    assertThat(unzipped.getKey()).isEqualTo("checkstyle");
-    assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
-    assertThat(unzipped.getMain()).isSameAs(jarFile);
-  }
-
-  @Test
-  public void unzip_plugin_without_libs() throws Exception {
-    File jarFile = temp.newFile();
-    final File toDir = temp.newFolder();
-    PluginInfo pluginInfo = new PluginInfo().setFile(jarFile);
-
-    PluginUnzipper unzipper = new PluginUnzipper() {
-      @Override
-      public UnzippedPlugin unzip(PluginInfo info) {
-        return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir);
-      }
-    };
-    UnzippedPlugin unzipped = unzipper.unzip(pluginInfo);
-    assertThat(unzipped.getKey()).isEqualTo("foo");
-    assertThat(unzipped.getLibs()).isEmpty();
-    assertThat(unzipped.getMain()).isSameAs(jarFile);
-  }
-
-  private File getFile(String filename) {
-    return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename));
-  }
-}
index 2d90503598d906053cd6794ca081ffabbe6768d0..1410b63ea509fdcc32269a8168d21a18f9ff295e 100644 (file)
@@ -118,5 +118,4 @@ public class FileCacheTest {
     assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir());
     assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by");
   }
-
 }