From 1b0e00bd132798475390145f52ab429ddb8c301c Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Wed, 6 May 2015 22:08:31 +0200 Subject: [PATCH] SONAR-6517 apply feedback --- .../computation/ComputationContainer.java | 10 +- .../db/migrations/DatabaseMigrator.java | 2 +- .../org/sonar/server/platform/Platform.java | 3 +- .../server/platform/ServerComponents.java | 4 +- .../server/plugins/PluginDownloader.java | 3 +- ...nzipper.java => ServerPluginExploder.java} | 16 +- .../plugins/ServerPluginRepository.java | 126 +++++--- .../debt/DebtModelPluginRepositoryTest.java | 23 +- ...InstalledPluginReferentialFactoryTest.java | 4 +- ...luginReferentialMetadataConverterTest.java | 6 +- ...est.java => ServerPluginExploderTest.java} | 16 +- .../plugins/ServerPluginRepositoryTest.java | 31 +- .../ws/InstalledPluginsWsActionTest.java | 22 +- .../ws/PendingPluginsWsActionTest.java | 91 +++--- .../plugins/ws/PluginWSCommonsTest.java | 78 ++--- .../startup/GeneratePluginIndexTest.java | 2 +- ...Unzipper.java => BatchPluginExploder.java} | 16 +- .../batch/bootstrap/BatchPluginInstaller.java | 6 +- .../batch/bootstrap/BatchPluginPredicate.java | 5 +- .../bootstrap/BatchPluginRepository.java | 20 +- .../batch/bootstrap/GlobalContainer.java | 2 +- ...Test.java => BatchPluginExploderTest.java} | 20 +- .../bootstrap/BatchPluginPredicateTest.java | 13 +- .../bootstrap/BatchPluginRepositoryTest.java | 291 ++++-------------- .../sonar/core/platform/ClassloaderDef.java | 97 ++++++ ...nzippedPlugin.java => ExplodedPlugin.java} | 18 +- ...luginUnzipper.java => PluginExploder.java} | 36 ++- .../org/sonar/core/platform/PluginInfo.java | 131 +++++--- .../org/sonar/core/platform/PluginLoader.java | 79 +++-- .../org/sonar/core/plugins/RemotePlugin.java | 6 +- ...ipperTest.java => PluginExploderTest.java} | 34 +- .../sonar/core/platform/PluginInfoTest.java | 38 +-- .../sonar/core/platform/PluginLoaderTest.java | 99 +++--- .../org/sonar/home/cache/FileCacheTest.java | 1 - 34 files changed, 705 insertions(+), 644 deletions(-) rename server/sonar-server/src/main/java/org/sonar/server/plugins/{ServerPluginUnzipper.java => ServerPluginExploder.java} (81%) rename server/sonar-server/src/test/java/org/sonar/server/plugins/{ServerPluginUnzipperTest.java => ServerPluginExploderTest.java} (83%) rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{BatchPluginUnzipper.java => BatchPluginExploder.java} (82%) rename sonar-batch/src/test/java/org/sonar/batch/bootstrap/{BatchPluginUnzipperTest.java => BatchPluginExploderTest.java} (83%) create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java rename sonar-core/src/main/java/org/sonar/core/platform/{UnzippedPlugin.java => ExplodedPlugin.java} (68%) rename sonar-core/src/main/java/org/sonar/core/platform/{PluginUnzipper.java => PluginExploder.java} (55%) rename sonar-core/src/test/java/org/sonar/core/platform/{PluginUnzipperTest.java => PluginExploderTest.java} (66%) diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java index d95084053ae..1ead2800482 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java @@ -19,9 +19,15 @@ */ 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; diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java index 7158b0d5e6d..bf47c6455ba 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java +++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java @@ -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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java index 4f28227339e..519f13ad49c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java @@ -19,14 +19,15 @@ */ 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; diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java index 6844878d9b9..c8594ed7438 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java @@ -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, diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java index b17ac977342..96fec742509 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java @@ -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 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/ServerPluginUnzipper.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java similarity index 81% rename from server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java rename to server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java index 59bc5a850fa..3c4865831c3 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java @@ -23,8 +23,8 @@ 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.core.platform.PluginExploder; +import org.sonar.core.platform.ExplodedPlugin; import org.sonar.server.platform.DefaultServerFileSystem; import java.io.File; @@ -32,11 +32,11 @@ 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 { +public class ServerPluginExploder extends PluginExploder implements ServerComponent { private final DefaultServerFileSystem fs; - public ServerPluginUnzipper(DefaultServerFileSystem fs) { + public ServerPluginExploder(DefaultServerFileSystem fs) { this.fs = fs; } @@ -46,20 +46,20 @@ public class ServerPluginUnzipper extends PluginUnzipper implements ServerCompon * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}. */ @Override - public UnzippedPlugin unzip(PluginInfo pluginInfo) { + public ExplodedPlugin explode(PluginInfo pluginInfo) { File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey()); try { forceMkdir(toDir); cleanDirectory(toDir); - File jarSource = pluginInfo.getFile(); + File jarSource = pluginInfo.getNonNullJarFile(); File jarTarget = new File(toDir, jarSource.getName()); FileUtils.copyFile(jarSource, jarTarget); ZipUtils.unzip(jarSource, toDir, newLibFilter()); - return UnzippedPlugin.createFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir); + 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.getFile().getAbsolutePath(), toDir.getAbsolutePath()), e); + "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 61258abec8e..4b6cd9e1937 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -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 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 removedKeys = new ArrayList<>(); + Set 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 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 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 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 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 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 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/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java index 730584d829b..0cd07b09c3e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java @@ -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 contributingPluginList = modelFinder.getContributingPluginList(); + Collection 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 contributingPluginList = modelFinder.getContributingPluginList(); + Collection 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 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 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() { diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java index 58c0e12c449..b027737c116 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java @@ -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)); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java index c7891203caa..8afe4e72f62 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java @@ -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/ServerPluginUnzipperTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java similarity index 83% rename from server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java rename to server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java index efea25d39e8..d67cbdeeed0 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java @@ -23,7 +23,7 @@ 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.core.platform.ExplodedPlugin; import org.sonar.server.platform.DefaultServerFileSystem; import java.io.File; @@ -32,13 +32,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class ServerPluginUnzipperTest { +public class ServerPluginExploderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class); - ServerPluginUnzipper underTest = new ServerPluginUnzipper(fs); + ServerPluginExploder underTest = new ServerPluginExploder(fs); @Test public void copy_all_classloader_files_to_dedicated_directory() throws Exception { @@ -47,16 +47,16 @@ public class ServerPluginUnzipperTest { File jar = TestProjectUtils.jarOf("test-libs-plugin"); PluginInfo info = PluginInfo.create(jar); - UnzippedPlugin unzipped = underTest.unzip(info); + 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(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(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()); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java index 8046379bca2..0e114a78f75 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java @@ -19,7 +19,10 @@ */ 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.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 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/ws/InstalledPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java index 11a544e1d23..1279f978501 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java @@ -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")); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java index 1849151312a..17586f0ec18 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java @@ -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); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java index e055635b343..c7e5151d57e 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java @@ -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"); + } + } diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java index 8af9cb1eb7c..8ae87df6f40 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java @@ -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/BatchPluginUnzipper.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java similarity index 82% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java index 29f554ddc89..3ecb3e244c8 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java @@ -22,30 +22,30 @@ 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.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 { +public class BatchPluginExploder extends PluginExploder implements BatchComponent { private final FileCache fileCache; - public BatchPluginUnzipper(FileCache fileCache) { + public BatchPluginExploder(FileCache fileCache) { this.fileCache = fileCache; } @Override - public UnzippedPlugin unzip(PluginInfo info) { + public ExplodedPlugin explode(PluginInfo info) { try { - File dir = unzipFile(info.getFile()); - return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), dir); + 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.getFile().getAbsolutePath()), e); + throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getNonNullJarFile().getAbsolutePath()), e); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java index 6e2c5886c60..190ad8a5e82 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -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 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 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); } } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java index 09cb7d391f0..0cd7f1c62e7 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java @@ -48,6 +48,7 @@ public class BatchPluginPredicate implements Predicate, 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 whites = newHashSet(), blacks = newHashSet(); private final DefaultAnalysisMode mode; @@ -75,10 +76,10 @@ public class BatchPluginPredicate implements Predicate, 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)); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java index b20c85114ed..37658fe8b1e 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java @@ -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 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/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java index 09d217122af..18be773ae8c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -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/BatchPluginUnzipperTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java similarity index 83% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java rename to sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java index 06a25148775..3d080e35d82 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java @@ -25,7 +25,7 @@ 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.core.platform.ExplodedPlugin; import org.sonar.home.cache.FileCache; import org.sonar.home.cache.FileCacheBuilder; @@ -34,29 +34,29 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; -public class BatchPluginUnzipperTest { +public class BatchPluginExploderTest { @ClassRule public static TemporaryFolder temp = new TemporaryFolder(); File userHome; - BatchPluginUnzipper underTest; + BatchPluginExploder underTest; @Before public void setUp() throws IOException { userHome = temp.newFolder(); FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build(); - underTest = new BatchPluginUnzipper(fileCache); + underTest = new BatchPluginExploder(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)); + ExplodedPlugin exploded = underTest.explode(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(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(); } @@ -64,7 +64,7 @@ public class BatchPluginUnzipperTest { @Test public void extract_only_libs() throws IOException { File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - underTest.unzip(PluginInfo.create(fileFromCache)); + 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(); @@ -72,7 +72,7 @@ public class BatchPluginUnzipperTest { } File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginUnzipperTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename)); + 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; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java index 95d9f18eb4c..57176404845 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -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") diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java index 7c82edbb64f..cafee2bebf2 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java @@ -17,237 +17,60 @@ * 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 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-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 index 00000000000..3ebe5ccb0fb --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java @@ -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 mainClassesByPluginKey = new HashMap<>(); + private final List 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 getMainClassesByPluginKey() { + return mainClassesByPluginKey; + } + + List 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 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/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java similarity index 68% rename from sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java rename to sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java index 3c73b8d658b..a5a9a500f59 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java @@ -21,17 +21,14 @@ 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 { +public class ExplodedPlugin { private final String key; private final File main; private final Collection libs; - public UnzippedPlugin(String key, File main, Collection libs) { + public ExplodedPlugin(String key, File main, Collection libs) { this.key = key; this.main = main; this.libs = libs; @@ -48,15 +45,4 @@ public class UnzippedPlugin { public Collection 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 libs; - if (libDir.isDirectory() && libDir.exists()) { - libs = listFiles(libDir, null, false); - } else { - libs = Collections.emptyList(); - } - return new UnzippedPlugin(pluginKey, jarFile, libs); - } } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java similarity index 55% rename from sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java rename to sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java index 5ce1ca08da7..50681f13e3d 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java @@ -19,22 +19,42 @@ */ 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; -public abstract class PluginUnzipper { +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 UnzippedPlugin unzip(PluginInfo info); + public abstract ExplodedPlugin explode(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); - } - }; + return ZipLibFilter.INSTANCE; + } + + protected ExplodedPlugin explodeFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) { + File libDir = new File(unzippedDir, PluginExploder.LIB_RELATIVE_PATH_IN_JAR); + Collection 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); + } } } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java index e5509bbc7e7..317ced0ba21 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java @@ -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 { + 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 { } 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 requiredPlugins = new ArrayList<>(); - public PluginInfo() { - } + private final Set 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 { return minimalSqVersion; } + @CheckForNull public String getMainClass() { return mainClass; } @@ -164,37 +223,15 @@ public class PluginInfo implements Comparable { return implementationBuild; } - public List getRequiredPlugins() { + public Set 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 { @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 { @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 { return info; } - public enum JarToPluginInfo implements Function { + private enum JarToPluginInfo implements Function { INSTANCE; @Override public PluginInfo apply(@Nonnull File jarFile) { return create(jarFile); } - }; + } + + public static Function jarToPluginInfo() { + return JarToPluginInfo.INSTANCE; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java index 336c4904bcf..b758f12d423 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java @@ -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, ...): + *
    + *
  • server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)
  • + *
  • batch loads only the plugins deployed on server
  • + *
*

* 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 mainClassesByPluginKey = new HashMap<>(); - final List 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 load(Map infoByKeys) { - Collection defs = defineClassloaders(infoByKeys).values(); + Collection 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 defineClassloaders(Map infoByKeys) { + @VisibleForTesting + Collection defineClassloaders(Map infoByKeys) { Map 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 defs) { + private void buildClassloaders(Collection 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 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 instantiatePluginInstances(Collection defs) { + private Map instantiatePluginInstances(Collection defs) { // instantiate plugins Map instancesByPluginKey = new HashMap<>(); for (ClassloaderDef def : defs) { // the same classloader can be used by multiple plugins - for (Map.Entry entry : def.mainClassesByPluginKey.entrySet()) { + for (Map.Entry 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/plugins/RemotePlugin.java b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java index 6fecfe0ae91..559faeb5a1a 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java +++ b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java @@ -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/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java similarity index 66% rename from sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java rename to sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java index cbdd9c23356..85906b1e8ce 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java @@ -29,7 +29,7 @@ import java.io.File; import static org.assertj.core.api.Assertions.assertThat; -public class PluginUnzipperTest { +public class PluginExploderTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @@ -38,41 +38,41 @@ public class PluginUnzipperTest { 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); + PluginInfo pluginInfo = new PluginInfo("checkstyle").setJarFile(jarFile); - PluginUnzipper unzipper = new PluginUnzipper() { + PluginExploder exploder = new PluginExploder() { @Override - public UnzippedPlugin unzip(PluginInfo info) { + public ExplodedPlugin explode(PluginInfo info) { try { ZipUtils.unzip(jarFile, toDir, newLibFilter()); - return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), toDir); + return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), 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); + 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().setFile(jarFile); + PluginInfo pluginInfo = new PluginInfo("foo").setJarFile(jarFile); - PluginUnzipper unzipper = new PluginUnzipper() { + PluginExploder exploder = new PluginExploder() { @Override - public UnzippedPlugin unzip(PluginInfo info) { - return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir); + public ExplodedPlugin explode(PluginInfo info) { + return explodeFromUnzippedDir("foo", info.getNonNullJarFile(), toDir); } }; - UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); - assertThat(unzipped.getKey()).isEqualTo("foo"); - assertThat(unzipped.getLibs()).isEmpty(); - assertThat(unzipped.getMain()).isSameAs(jarFile); + ExplodedPlugin exploded = exploder.explode(pluginInfo); + assertThat(exploded.getKey()).isEqualTo("foo"); + assertThat(exploded.getLibs()).isEmpty(); + assertThat(exploded.getMain()).isSameAs(jarFile); } private File getFile(String filename) { diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java index 2b702748c4b..198f9e0c9f1 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java @@ -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 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 plugins = Arrays.asList(java1, java2, cobol, noVersion); Collections.shuffle(plugins); List 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)); diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java index 7cb984b1bda..c32fc2f711a 100644 --- a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java @@ -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 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 defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + PluginLoader loader = new PluginLoader(new FakePluginExploder()); + Collection 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 defs = loader.defineClassloaders(ImmutableMap.of("foo", base, "fooContrib", extension)); + PluginLoader loader = new PluginLoader(new FakePluginExploder()); + + Collection 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.emptyList()); + public ExplodedPlugin explode(PluginInfo info) { + return new ExplodedPlugin(info.getKey(), info.getNonNullJarFile(), Collections.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-home/src/test/java/org/sonar/home/cache/FileCacheTest.java b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java index 2d90503598d..1410b63ea50 100644 --- a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java +++ b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java @@ -118,5 +118,4 @@ public class FileCacheTest { assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by"); } - } -- 2.39.5