From: Simon Brandhof Date: Fri, 24 Apr 2015 07:15:05 +0000 (+0200) Subject: SONAR-6370 isolate plugin classloader from core X-Git-Tag: 5.2-RC1~2007 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=14a5c982e5f1b28354a853073bd3e225b3914abe;p=sonarqube.git SONAR-6370 isolate plugin classloader from core --- diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java index 080a6b643ec..6d9fc80d519 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.plugins.core.charts; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java index 30550df1482..d901586ae25 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.plugins.core.dashboards; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java index a2cb7d768c1..5e28de8ee2c 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.plugins.core.notifications.alerts; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java index 03fd8c46350..df9ccea5073 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.plugins.core.security; diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java index 870522ec56f..8d487ae9500 100644 --- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java +++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.plugins.core.timemachine; diff --git a/pom.xml b/pom.xml index 2dee8a71b58..3d6388fb8b0 100644 --- a/pom.xml +++ b/pom.xml @@ -595,6 +595,11 @@ sonar-channel 4.1 + + org.codehaus.sonar + sonar-classloader + 1.0 + org.codehaus.sonar sonar-markdown @@ -707,11 +712,6 @@ - - org.codehaus.plexus - plexus-classworlds - 2.5.1 - com.tinkerpop.blueprints blueprints-core diff --git a/server/sonar-server/pom.xml b/server/sonar-server/pom.xml index 78e7f995a1e..fd3ee697d4f 100644 --- a/server/sonar-server/pom.xml +++ b/server/sonar-server/pom.xml @@ -248,7 +248,19 @@ src/main/resources true + + + + src/test/resources + false + + + src/test/projects + false + + + 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 95ad3ef370e..d95084053ae 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,7 +19,7 @@ */ package org.sonar.server.computation; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.core.issue.db.UpdateConflictResolver; import org.sonar.server.computation.issue.*; import org.sonar.server.computation.measure.MetricCache; 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 b506fc5244a..7158b0d5e6d 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 @@ -29,9 +29,9 @@ import org.sonar.api.platform.ServerUpgradeStatus; import org.sonar.api.utils.log.Loggers; import org.sonar.core.persistence.DdlUtils; import org.sonar.server.db.DbClient; -import org.sonar.server.plugins.ServerPluginRepository; import com.google.common.annotations.VisibleForTesting; +import org.sonar.server.plugins.ServerPluginRepository; /** * Restore schema by executing DDL scripts. Only H2 database is supported. @@ -46,10 +46,10 @@ public class DatabaseMigrator implements ServerComponent, Startable { private final ServerUpgradeStatus serverUpgradeStatus; /** - * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done + * ServerPluginInstaller 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 serverPluginRepository) { + ServerPluginRepository unused) { this.dbClient = dbClient; this.migrations = migrations; this.serverUpgradeStatus = serverUpgradeStatus; diff --git a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java index b0e1cb6bd20..da45c47bf68 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java @@ -25,9 +25,9 @@ import com.google.common.collect.Maps; import org.apache.commons.io.Charsets; import org.picocontainer.Startable; import org.sonar.api.Plugin; -import org.sonar.api.ServerExtension; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.api.ServerComponent; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.io.InputStreamReader; import java.io.Reader; @@ -45,7 +45,7 @@ import static com.google.common.collect.Lists.newArrayList; * they must be named "-model.xml". *

*/ -public class DebtModelPluginRepository implements ServerExtension, Startable { +public class DebtModelPluginRepository implements ServerComponent, Startable { public static final String DEFAULT_MODEL = "technical-debt"; @@ -87,14 +87,12 @@ public class DebtModelPluginRepository implements ServerExtension, Startable { contributingPluginKeyToClassLoader = Maps.newTreeMap(); // Add default model contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader()); - for (PluginMetadata metadata : pluginRepository.getMetadata()) { - String pluginKey = metadata.getKey(); - Plugin plugin = pluginRepository.getPlugin(pluginKey); - if (plugin != null) { - ClassLoader classLoader = plugin.getClass().getClassLoader(); - if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) { - contributingPluginKeyToClassLoader.put(pluginKey, classLoader); - } + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + String pluginKey = pluginInfo.getKey(); + Plugin plugin = pluginRepository.getPluginInstance(pluginKey); + ClassLoader classLoader = plugin.getClass().getClassLoader(); + if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) { + contributingPluginKeyToClassLoader.put(pluginKey, classLoader); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java index df520c3900a..5258e753057 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java @@ -127,27 +127,16 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable { return new File(getHomeDir(), "extensions/downloads"); } - public File getTrashPluginsDir() { - return new File(getHomeDir(), "extensions/trash"); - } - - public List getCorePlugins() { - File corePluginsDir = new File(getHomeDir(), "lib/core-plugins"); - return getFiles(corePluginsDir, "jar"); - } - - public List getBundledPlugins() { - File corePluginsDir = new File(getHomeDir(), "lib/bundled-plugins"); - return getFiles(corePluginsDir, "jar"); + public File getInstalledPluginsDir() { + return new File(getHomeDir(), "extensions/plugins"); } - public List getUserPlugins() { - File pluginsDir = getUserPluginsDir(); - return getFiles(pluginsDir, "jar"); + public File getBundledPluginsDir() { + return new File(getHomeDir(), "lib/bundled-plugins"); } - public File getUserPluginsDir() { - return new File(getHomeDir(), "extensions/plugins"); + public File getCorePluginsDir() { + return new File(getHomeDir(), "lib/core-plugins"); } public File getDeprecatedPluginsDir() { @@ -172,7 +161,7 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable { } private List getFiles(File dir, String... fileSuffixes) { - List files = new ArrayList(); + List files = new ArrayList<>(); if (dir != null && dir.exists()) { if (fileSuffixes != null && fileSuffixes.length > 0) { files.addAll(FileUtils.listFiles(dir, fileSuffixes, false)); 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 5d857910be2..4f28227339e 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,16 +19,17 @@ */ package org.sonar.server.platform; -import java.util.Collection; -import java.util.Properties; -import javax.annotation.CheckForNull; -import javax.servlet.ServletContext; -import org.sonar.api.platform.ComponentContainer; +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 javax.annotation.CheckForNull; +import javax.servlet.ServletContext; +import java.util.Collection; +import java.util.Properties; + /** * @since 2.2 */ diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java index c034a78ee38..8f7cb251897 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java @@ -25,11 +25,11 @@ import org.apache.commons.io.FileUtils; import org.apache.commons.lang.StringUtils; import org.picocontainer.Startable; import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.platform.ServerFileSystem; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import javax.annotation.Nullable; @@ -47,28 +47,26 @@ public class RailsAppsDeployer implements Startable { private static final Logger LOG = Loggers.get(RailsAppsDeployer.class); private static final String ROR_PATH = "org/sonar/ror/"; - private final ServerFileSystem fileSystem; + private final ServerFileSystem fs; private final PluginRepository pluginRepository; - public RailsAppsDeployer(ServerFileSystem fileSystem, PluginRepository pluginRepository) { - this.fileSystem = fileSystem; + public RailsAppsDeployer(ServerFileSystem fs, PluginRepository pluginRepository) { + this.fs = fs; this.pluginRepository = pluginRepository; } @Override public void start() { - LOG.info("Deploy Ruby on Rails applications"); + LOG.info("Deploying Ruby on Rails applications"); File appsDir = prepareRailsDirectory(); - for (PluginMetadata pluginMetadata : pluginRepository.getMetadata()) { - String pluginKey = pluginMetadata.getKey(); - Plugin plugin = pluginRepository.getPlugin(pluginKey); - if (plugin != null) { - try { - deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader()); - } catch (Exception e) { - throw new IllegalStateException("Fail to deploy Ruby on Rails application: " + pluginKey, e); - } + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + String pluginKey = pluginInfo.getKey(); + Plugin plugin = pluginRepository.getPluginInstance(pluginKey); + try { + deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader()); + } catch (Exception e) { + throw new IllegalStateException(String.format("Fail to deploy Ruby on Rails application of plugin [%s]", pluginKey), e); } } } @@ -80,7 +78,7 @@ public class RailsAppsDeployer implements Startable { @VisibleForTesting File prepareRailsDirectory() { - File appsDir = new File(fileSystem.getTempDir(), "ror"); + File appsDir = new File(fs.getTempDir(), "ror"); prepareDir(appsDir); return appsDir; } @@ -88,7 +86,7 @@ public class RailsAppsDeployer implements Startable { @VisibleForTesting static void deployRailsApp(File appsDir, final String pluginKey, ClassLoader appClassLoader) { if (hasRailsApp(pluginKey, appClassLoader)) { - LOG.info("Deploy app: " + pluginKey); + LOG.info("Deploying app: " + pluginKey); File appDir = new File(appsDir, pluginKey); ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, new Function() { @Override 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 b6347f84e61..6844878d9b9 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 @@ -22,7 +22,6 @@ package org.sonar.server.platform; import com.google.common.collect.Lists; import org.sonar.api.config.EmailSettings; import org.sonar.api.issue.action.Actions; -import org.sonar.api.platform.ComponentContainer; import org.sonar.api.profiles.AnnotationProfileParser; import org.sonar.api.profiles.XMLProfileParser; import org.sonar.api.profiles.XMLProfileSerializer; @@ -58,6 +57,8 @@ import org.sonar.core.persistence.DefaultDatabase; import org.sonar.core.persistence.MyBatis; import org.sonar.core.persistence.SemaphoreUpdater; import org.sonar.core.persistence.SemaphoresImpl; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginLoader; import org.sonar.core.purge.PurgeProfiler; import org.sonar.core.qualitygate.db.ProjectQgateAssociationDao; import org.sonar.core.qualitygate.db.QualityGateConditionDao; @@ -216,9 +217,8 @@ import org.sonar.server.platform.ws.UpgradesSystemWsAction; import org.sonar.server.plugins.InstalledPluginReferentialFactory; import org.sonar.server.plugins.PluginDownloader; import org.sonar.server.plugins.ServerExtensionInstaller; -import org.sonar.server.plugins.ServerPluginJarInstaller; -import org.sonar.server.plugins.ServerPluginJarsInstaller; import org.sonar.server.plugins.ServerPluginRepository; +import org.sonar.server.plugins.ServerPluginUnzipper; import org.sonar.server.plugins.UpdateCenterClient; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.plugins.ws.AvailablePluginsWsAction; @@ -519,10 +519,10 @@ class ServerComponents { PlatformRubyBridge.class, // plugins - ServerPluginJarsInstaller.class, - ServerPluginJarInstaller.class, - InstalledPluginReferentialFactory.class, ServerPluginRepository.class, + ServerPluginUnzipper.class, + PluginLoader.class, + InstalledPluginReferentialFactory.class, ServerExtensionInstaller.class, // depends on plugins diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java index 9ec58dc4044..591168f2831 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java @@ -22,12 +22,13 @@ package org.sonar.server.platform.monitoring; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.Nonnull; import java.util.LinkedHashMap; -import java.util.List; /** * Installed plugins (excluding core plugins) @@ -47,21 +48,24 @@ public class PluginsMonitor implements Monitor { @Override public LinkedHashMap attributes() { LinkedHashMap attributes = new LinkedHashMap<>(); - for (PluginMetadata plugin : plugins()) { + for (PluginInfo plugin : plugins()) { LinkedHashMap pluginAttributes = new LinkedHashMap<>(); pluginAttributes.put("Name", plugin.getName()); - pluginAttributes.put("Version", plugin.getVersion()); + Version version = plugin.getVersion(); + if (version != null) { + pluginAttributes.put("Version", version.getName()); + } attributes.put(plugin.getKey(), pluginAttributes); } return attributes; } - private List plugins() { - return Lists.newArrayList(Iterables.filter(repository.getMetadata(), new Predicate() { + private Iterable plugins() { + return Iterables.filter(repository.getPluginInfos(), new Predicate() { @Override - public boolean apply(PluginMetadata input) { - return !input.isCore(); + public boolean apply(@Nonnull PluginInfo info) { + return !info.isCore(); } - })); + }); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java index 257cd22ca05..599899c475c 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java @@ -20,7 +20,7 @@ package org.sonar.server.plugins; import org.picocontainer.Startable; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginRepository; import org.sonar.updatecenter.common.PluginReferential; public class InstalledPluginReferentialFactory implements Startable { @@ -51,7 +51,7 @@ public class InstalledPluginReferentialFactory implements Startable { } private void init() { - installedPluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(pluginRepository.getMetadata()); + installedPluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(pluginRepository.getPluginInfos()); } } 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 1e2dd190f49..b17ac977342 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 @@ -25,7 +25,7 @@ import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.SonarException; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.server.platform.DefaultServerFileSystem; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.Version; @@ -48,6 +48,10 @@ import static org.apache.commons.io.FileUtils.forceMkdir; import static org.apache.commons.io.FileUtils.toFile; import static org.apache.commons.lang.StringUtils.substringAfterLast; +/** + * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then + * moved to extensions/plugins after server restart. + */ public class PluginDownloader implements Startable { private static final Logger LOG = Loggers.get(PluginDownloader.class); @@ -56,21 +60,17 @@ public class PluginDownloader implements Startable { private final UpdateCenterMatrixFactory updateCenterMatrixFactory; private final HttpDownloader downloader; - private final ServerPluginJarInstaller installer; private final File downloadDir; public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, - DefaultServerFileSystem fileSystem, ServerPluginJarInstaller installer) { + DefaultServerFileSystem fileSystem) { this.updateCenterMatrixFactory = updateCenterMatrixFactory; this.downloader = downloader; - this.installer = installer; this.downloadDir = fileSystem.getDownloadedPluginsDir(); } /** - * Delete the temporary files remaining from previous downloads - * - * @see #downloadRelease(org.sonar.updatecenter.common.Release) + * Deletes the temporary files remaining from previous downloads */ @Override public void start() { @@ -112,10 +112,10 @@ public class PluginDownloader implements Startable { } /** - * @return the list of download plugins as {@link DefaultPluginMetadata} instances + * @return the list of download plugins as {@link PluginInfo} instances */ - public Collection getDownloadedPlugins() { - return newArrayList(transform(listPlugins(this.downloadDir), installer.fileToPlugin())); + public Collection getDownloadedPlugins() { + return newArrayList(transform(listPlugins(this.downloadDir), PluginInfo.JarToPluginInfo.INSTANCE)); } public void download(String pluginKey, Version version) { @@ -154,10 +154,10 @@ public class PluginDownloader implements Startable { } private static Collection listTempFile(File dir) { - return FileUtils.listFiles(dir, new String[]{TMP_SUFFIX}, false); + return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false); } private static Collection listPlugins(File dir) { - return FileUtils.listFiles(dir, new String[]{PLUGIN_EXTENSION}, false); + return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java index 30646c02a95..70afcb42068 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java @@ -19,10 +19,11 @@ */ package org.sonar.server.plugins; -import org.sonar.api.platform.PluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.updatecenter.common.PluginManifest; import org.sonar.updatecenter.common.PluginReferential; import org.sonar.updatecenter.common.PluginReferentialManifestConverter; +import org.sonar.updatecenter.common.Version; import java.util.Collection; import java.util.List; @@ -35,14 +36,14 @@ public class PluginReferentialMetadataConverter { // Only static call } - public static PluginReferential getInstalledPluginReferential(Collection metadata) { + public static PluginReferential getInstalledPluginReferential(Collection metadata) { List pluginManifestList = getPluginManifestList(metadata); return PluginReferentialManifestConverter.fromPluginManifests(pluginManifestList); } - private static List getPluginManifestList(Collection metadata) { + private static List getPluginManifestList(Collection metadata) { List pluginManifestList = newArrayList(); - for (PluginMetadata plugin : metadata) { + for (PluginInfo plugin : metadata) { if (!plugin.isCore()) { pluginManifestList.add(toPluginManifest(plugin)); } @@ -50,20 +51,23 @@ public class PluginReferentialMetadataConverter { return pluginManifestList; } - private static PluginManifest toPluginManifest(PluginMetadata metadata) { + private static PluginManifest toPluginManifest(PluginInfo metadata) { PluginManifest pluginManifest = new PluginManifest(); pluginManifest.setKey(metadata.getKey()); pluginManifest.setName(metadata.getName()); - pluginManifest.setVersion(metadata.getVersion()); + Version version = metadata.getVersion(); + if (version != null) { + pluginManifest.setVersion(version.getName()); + } pluginManifest.setDescription(metadata.getDescription()); pluginManifest.setMainClass(metadata.getMainClass()); - pluginManifest.setOrganization(metadata.getOrganization()); + pluginManifest.setOrganization(metadata.getOrganizationName()); pluginManifest.setOrganizationUrl(metadata.getOrganizationUrl()); pluginManifest.setLicense(metadata.getLicense()); - pluginManifest.setHomepage(metadata.getHomepage()); + pluginManifest.setHomepage(metadata.getHomepageUrl()); pluginManifest.setIssueTrackerUrl(metadata.getIssueTrackerUrl()); pluginManifest.setBasePlugin(metadata.getBasePlugin()); - pluginManifest.setRequirePlugins(metadata.getRequiredPlugins().toArray(new String []{})); + pluginManifest.setRequirePlugins(metadata.getRequiredPlugins().toArray(new String[] {})); return pluginManifest; } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java index c084470711c..e9297de4353 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java @@ -24,40 +24,43 @@ import com.google.common.collect.ListMultimap; import org.sonar.api.Extension; import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; +import org.sonar.api.ServerComponent; import org.sonar.api.ServerExtension; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.util.Map; /** - * This class adds to picocontainer the server extensions provided by plugins + * Loads the plugins server extensions and injects them to DI container */ -public class ServerExtensionInstaller { - private PluginRepository pluginRepository; +public class ServerExtensionInstaller implements ServerComponent { + + private final PluginRepository pluginRepository; public ServerExtensionInstaller(PluginRepository pluginRepository) { this.pluginRepository = pluginRepository; } public void installExtensions(ComponentContainer container) { - ListMultimap installedExtensionsByPlugin = ArrayListMultimap.create(); + ListMultimap installedExtensionsByPlugin = ArrayListMultimap.create(); - for (PluginMetadata pluginMetadata : pluginRepository.getMetadata()) { - Plugin plugin = pluginRepository.getPlugin(pluginMetadata.getKey()); - container.addExtension(pluginMetadata, plugin); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + String pluginKey = pluginInfo.getKey(); + Plugin plugin = pluginRepository.getPluginInstance(pluginKey); + container.addExtension(pluginInfo, plugin); for (Object extension : plugin.getExtensions()) { - if (installExtension(container, pluginMetadata, extension, true) != null) { - installedExtensionsByPlugin.put(pluginMetadata, extension); + if (installExtension(container, pluginInfo, extension, true) != null) { + installedExtensionsByPlugin.put(pluginInfo, extension); } else { - container.declareExtension(pluginMetadata, extension); + container.declareExtension(pluginInfo, extension); } } } - for (Map.Entry entry : installedExtensionsByPlugin.entries()) { - PluginMetadata plugin = entry.getKey(); + for (Map.Entry entry : installedExtensionsByPlugin.entries()) { + PluginInfo plugin = entry.getKey(); Object extension = entry.getValue(); if (isExtensionProvider(extension)) { ExtensionProvider provider = (ExtensionProvider) container.getComponentByKey(extension); @@ -66,25 +69,25 @@ public class ServerExtensionInstaller { } } - private void installProvider(ComponentContainer container, PluginMetadata plugin, ExtensionProvider provider) { + private void installProvider(ComponentContainer container, PluginInfo pluginInfo, ExtensionProvider provider) { Object obj = provider.provide(); if (obj != null) { if (obj instanceof Iterable) { for (Object ext : (Iterable) obj) { - installExtension(container, plugin, ext, false); + installExtension(container, pluginInfo, ext, false); } } else { - installExtension(container, plugin, obj, false); + installExtension(container, pluginInfo, obj, false); } } } - Object installExtension(ComponentContainer container, PluginMetadata pluginMetadata, Object extension, boolean acceptProvider) { + Object installExtension(ComponentContainer container, PluginInfo pluginInfo, Object extension, boolean acceptProvider) { if (isType(extension, ServerExtension.class)) { if (!acceptProvider && isExtensionProvider(extension)) { throw new IllegalStateException("ExtensionProvider can not include providers itself: " + extension); } - container.addExtension(pluginMetadata, extension); + container.addExtension(pluginInfo, extension); return extension; } return null; diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java deleted file mode 100644 index 777a197defd..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java +++ /dev/null @@ -1,62 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.plugins; - -import org.apache.commons.io.FileUtils; -import org.sonar.api.utils.ZipUtils; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.PluginJarInstaller; - -import java.io.File; -import java.io.IOException; -import java.util.zip.ZipEntry; - -public class ServerPluginJarInstaller extends PluginJarInstaller { - - public void installToDir(DefaultPluginMetadata metadata, File pluginBasedir) { - try { - File pluginFile = metadata.getFile(); - File deployedPlugin = copyPlugin(pluginBasedir, pluginFile); - install(metadata, pluginBasedir, deployedPlugin); - } catch (IOException e) { - throw new IllegalStateException(FAIL_TO_INSTALL_PLUGIN + metadata, e); - } - } - - private File copyPlugin(File pluginBasedir, File pluginFile) throws IOException { - FileUtils.forceMkdir(pluginBasedir); - File targetFile = new File(pluginBasedir, pluginFile.getName()); - FileUtils.copyFile(pluginFile, targetFile); - return targetFile; - } - - @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - ZipUtils.unzip(pluginFile, pluginBasedir, new LibFilter()); - return pluginBasedir; - } - - private static final class LibFilter implements ZipUtils.ZipEntryFilter { - @Override - public boolean accept(ZipEntry entry) { - return entry.getName().startsWith("META-INF/lib"); - } - } -} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java deleted file mode 100644 index 9b9ce32dc81..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java +++ /dev/null @@ -1,286 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.plugins; - -import com.google.common.base.Function; -import com.google.common.base.Joiner; -import com.google.common.collect.Maps; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.Server; -import org.sonar.api.platform.ServerUpgradeStatus; -import org.sonar.api.utils.MessageException; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; -import org.sonar.api.utils.log.Profiler; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.server.platform.DefaultServerFileSystem; -import org.sonar.updatecenter.common.PluginReferential; - -import javax.annotation.Nonnull; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import static com.google.common.collect.Iterables.transform; -import static com.google.common.collect.Lists.newArrayList; -import static java.lang.String.format; -import static org.apache.commons.io.FileUtils.cleanDirectory; -import static org.apache.commons.io.FileUtils.copyFile; -import static org.apache.commons.io.FileUtils.deleteDirectory; -import static org.apache.commons.io.FileUtils.deleteQuietly; -import static org.apache.commons.io.FileUtils.forceMkdir; -import static org.apache.commons.io.FileUtils.listFiles; -import static org.apache.commons.io.FileUtils.moveFile; -import static org.apache.commons.io.FileUtils.moveFileToDirectory; -import static org.apache.commons.lang.StringUtils.isNotBlank; - -public class ServerPluginJarsInstaller { - - private static final Logger LOG = Loggers.get(ServerPluginJarsInstaller.class); - private static final String FILE_EXTENSION_JAR = "jar"; - private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls(); - - private final Server server; - private final DefaultServerFileSystem fs; - private final ServerPluginJarInstaller installer; - private final Map pluginByKeys = Maps.newHashMap(); - private final ServerUpgradeStatus serverUpgradeStatus; - private static final Set BLACKLISTED_PLUGINS = new HashSet(Arrays.asList("scmactivity", "issuesreport")); - - public ServerPluginJarsInstaller(Server server, ServerUpgradeStatus serverUpgradeStatus, - DefaultServerFileSystem fs, ServerPluginJarInstaller installer) { - this.server = server; - this.serverUpgradeStatus = serverUpgradeStatus; - this.fs = fs; - this.installer = installer; - } - - public void install() { - Profiler profiler = Profiler.create(LOG).startInfo("Install plugins"); - deleteTrash(); - loadInstalledPlugins(); - copyBundledPlugins(); - moveDownloadedPlugins(); - loadCorePlugins(); - deployPlugins(); - profiler.stopDebug(); - } - - private void deleteTrash() { - File trashDir = fs.getTrashPluginsDir(); - try { - if (trashDir.exists()) { - deleteDirectory(trashDir); - } - } catch (IOException e) { - throw new IllegalStateException("Fail to clean the plugin trash directory: " + trashDir, e); - } - } - - private void loadInstalledPlugins() { - for (File file : fs.getUserPlugins()) { - PluginMetadata metadata = installer.fileToPlugin().apply(file); - if (isNotBlank(metadata.getKey())) { - loadInstalledPlugin(metadata); - } - } - } - - private void loadInstalledPlugin(PluginMetadata metadata) { - if (BLACKLISTED_PLUGINS.contains(metadata.getKey())) { - LOG.warn("Plugin {} is blacklisted. Please uninstall it.", metadata.getName()); - } else { - PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata); - if (existing != null) { - throw MessageException.of(format("Found two files for the same plugin '%s': %s and %s", - metadata.getKey(), metadata.getFile().getName(), existing.getFile().getName())); - } - } - } - - private void moveDownloadedPlugins() { - if (fs.getDownloadedPluginsDir().exists()) { - for (File sourceFile : listJarFiles(fs.getDownloadedPluginsDir())) { - overridePlugin(sourceFile, true); - } - } - } - - private void copyBundledPlugins() { - if (serverUpgradeStatus.isFreshInstall()) { - for (File sourceFile : fs.getBundledPlugins()) { - PluginMetadata metadata = installer.fileToPlugin().apply(sourceFile); - // lib/bundled-plugins should be copied only if the plugin is not already - // available in extensions/plugins - if (!pluginByKeys.containsKey(metadata.getKey())) { - overridePlugin(sourceFile, false); - } - } - } - } - - private void overridePlugin(File sourceFile, boolean deleteSource) { - File destDir = fs.getUserPluginsDir(); - File destFile = new File(destDir, sourceFile.getName()); - if (destFile.exists()) { - // plugin with same filename already installed - deleteQuietly(destFile); - } - - try { - if (deleteSource) { - moveFile(sourceFile, destFile); - } else { - copyFile(sourceFile, destFile, true); - } - } catch (IOException e) { - LOG.error(format("Fail to move or copy plugin: %s to %s", - sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e); - } - - PluginMetadata metadata = installer.fileToPlugin().apply(destFile); - if (isNotBlank(metadata.getKey())) { - PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata); - if (existing != null) { - if (!existing.getFile().getName().equals(destFile.getName())) { - deleteQuietly(existing.getFile()); - } - LOG.info("Plugin " + metadata.getKey() + " replaced by new version"); - } - } - } - - private void loadCorePlugins() { - for (File file : fs.getCorePlugins()) { - PluginMetadata metadata = installer.fileToCorePlugin().apply(file); - PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata); - if (existing != null) { - throw new IllegalStateException("Found two plugins with the same key '" + metadata.getKey() + "': " + metadata.getFile().getName() + " and " - + existing.getFile().getName()); - } - } - } - - public void uninstall(String pluginKey) { - for (String key : getPluginReferential().findLastReleasesWithDependencies(pluginKey)) { - uninstallPlugin(key); - } - } - - private void uninstallPlugin(String pluginKey) { - PluginMetadata metadata = pluginByKeys.get(pluginKey); - if (metadata != null && !metadata.isCore()) { - try { - File masterFile = new File(fs.getUserPluginsDir(), metadata.getFile().getName()); - moveFileToDirectory(masterFile, fs.getTrashPluginsDir(), true); - } catch (IOException e) { - throw new IllegalStateException("Fail to uninstall plugin: " + pluginKey, e); - } - } - } - - public List getUninstalledPluginFilenames() { - if (!fs.getTrashPluginsDir().exists()) { - return Collections.emptyList(); - } - - return newArrayList(transform(listJarFiles(fs.getTrashPluginsDir()), FileToName.INSTANCE)); - } - - /** - * @return the list of plugins to be uninstalled as {@link DefaultPluginMetadata} instances - */ - public Collection getUninstalledPlugins() { - if (!fs.getTrashPluginsDir().exists()) { - return Collections.emptyList(); - } - - return newArrayList(transform(listJarFiles(fs.getTrashPluginsDir()), installer.fileToPlugin())); - } - - public void cancelUninstalls() { - if (fs.getTrashPluginsDir().exists()) { - for (File file : listJarFiles(fs.getTrashPluginsDir())) { - try { - moveFileToDirectory(file, fs.getUserPluginsDir(), false); - } catch (IOException e) { - throw new IllegalStateException("Fail to cancel plugin uninstalls", e); - } - } - } - } - - private void deployPlugins() { - for (PluginMetadata metadata : pluginByKeys.values()) { - deploy((DefaultPluginMetadata) metadata); - } - } - - private void deploy(DefaultPluginMetadata plugin) { - LOG.info("Deploy plugin {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild())); - - if (!plugin.isCompatibleWith(server.getVersion())) { - throw MessageException.of(format( - "Plugin %s needs a more recent version of SonarQube than %s. At least %s is expected", - plugin.getKey(), server.getVersion(), plugin.getSonarVersion())); - } - - try { - File pluginDeployDir = new File(fs.getDeployedPluginsDir(), plugin.getKey()); - forceMkdir(pluginDeployDir); - cleanDirectory(pluginDeployDir); - - installer.installToDir(plugin, pluginDeployDir); - } catch (IOException e) { - throw new IllegalStateException("Fail to deploy the plugin " + plugin, e); - } - } - - public Collection getMetadata() { - return pluginByKeys.values(); - } - - public PluginMetadata getMetadata(String pluginKey) { - return pluginByKeys.get(pluginKey); - } - - private PluginReferential getPluginReferential() { - return PluginReferentialMetadataConverter.getInstalledPluginReferential(getMetadata()); - } - - private static Collection listJarFiles(File pluginDir) { - return listFiles(pluginDir, new String[] {FILE_EXTENSION_JAR}, false); - } - - private enum FileToName implements Function { - INSTANCE; - - @Override - public String apply(@Nonnull File file) { - return file.getName(); - } - } -} 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 16028617966..cb9db1f4042 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 @@ -19,76 +19,361 @@ */ package org.sonar.server.plugins; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +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 org.apache.commons.io.FileUtils; import org.picocontainer.Startable; -import org.sonar.api.utils.log.Loggers; import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.PluginClassloaders; +import org.sonar.api.platform.Server; +import org.sonar.api.platform.ServerUpgradeStatus; +import org.sonar.api.utils.MessageException; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; +import org.sonar.server.platform.DefaultServerFileSystem; + +import javax.annotation.Nonnull; -import javax.annotation.CheckForNull; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; import java.util.Map; +import java.util.Set; + +import static com.google.common.collect.Iterables.transform; +import static com.google.common.collect.Lists.newArrayList; +import static java.lang.String.format; +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; /** - * This class loads JAR files and initializes classloaders of plugins + * Manages installation and loading of plugins: + *
    + *
  • installation of bundled plugins on first server startup
  • + *
  • installation of new plugins (effective after server startup)
  • + *
  • un-installation of plugins (effective after server startup)
  • + *
  • cancel pending installations/un-installations
  • + *
  • load plugin bytecode
  • + *
*/ public class ServerPluginRepository implements PluginRepository, Startable { - private final ServerPluginJarsInstaller jarsInstaller; - private final PluginClassloaders classloaders; - private Map pluginsByKey; + private static final Logger LOG = Loggers.get(ServerPluginRepository.class); + private static final String FILE_EXTENSION_JAR = "jar"; + private static final Set DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport"); + private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls(); + + private final Server server; + private final DefaultServerFileSystem fs; + private final ServerUpgradeStatus upgradeStatus; + private final PluginLoader loader; + private Set blacklistedPluginKeys = DEFAULT_BLACKLISTED_PLUGINS; - public ServerPluginRepository(ServerPluginJarsInstaller jarsInstaller) { - this.classloaders = new PluginClassloaders(getClass().getClassLoader()); - this.jarsInstaller = jarsInstaller; + // following fields are available after startup + private final Map pluginInfosByKeys = new HashMap<>(); + private final Map pluginInstancesByKeys = new HashMap<>(); + + public ServerPluginRepository(Server server, ServerUpgradeStatus upgradeStatus, + DefaultServerFileSystem fs, PluginLoader loader) { + this.server = server; + this.upgradeStatus = upgradeStatus; + this.fs = fs; + this.loader = loader; + } + + @VisibleForTesting + void setBlacklistedPluginKeys(Set keys) { + this.blacklistedPluginKeys = keys; } @Override public void start() { - jarsInstaller.install(); - Collection metadata = jarsInstaller.getMetadata(); - pluginsByKey = classloaders.init(metadata); + loadPreInstalledPlugins(); + copyBundledPlugins(); + moveDownloadedPlugins(); + loadCorePlugins(); + unloadIncompatiblePlugins(); + logInstalledPlugins(); + loadInstances(); } @Override public void stop() { - classloaders.clean(); + // close classloaders + loader.unload(pluginInstancesByKeys.values()); + + pluginInstancesByKeys.clear(); + pluginInfosByKeys.clear(); } - @Override - public Plugin getPlugin(String key) { - return pluginsByKey.get(key); + /** + * Load the plugins that are located in extensions/plugins. Blacklisted plugins are + * deleted. + */ + private void loadPreInstalledPlugins() { + for (File file : listJarFiles(fs.getInstalledPluginsDir())) { + PluginInfo info = PluginInfo.create(file); + registerPluginInfo(info); + } + } + + /** + * Move the plugins recently downloaded to extensions/plugins. + */ + private void moveDownloadedPlugins() { + if (fs.getDownloadedPluginsDir().exists()) { + for (File sourceFile : listJarFiles(fs.getDownloadedPluginsDir())) { + overrideAndRegisterPlugin(sourceFile, true); + } + } + } + + /** + * Copies the plugins bundled with SonarQube distribution to directory extensions/plugins. + * Does nothing if not a fresh installation. + */ + private void copyBundledPlugins() { + if (upgradeStatus.isFreshInstall()) { + for (File sourceFile : listJarFiles(fs.getBundledPluginsDir())) { + PluginInfo info = PluginInfo.create(sourceFile); + // lib/bundled-plugins should be copied only if the plugin is not already + // available in extensions/plugins + if (!pluginInfosByKeys.containsKey(info.getKey())) { + overrideAndRegisterPlugin(sourceFile, false); + } + } + } + } + + 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()); + 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())); + } + + } + + /** + * Move or copy plugin to directory extensions/plugins. If a version of this plugin + * already exists then it's deleted. + */ + private void overrideAndRegisterPlugin(File sourceFile, boolean deleteSource) { + File destDir = fs.getInstalledPluginsDir(); + File destFile = new File(destDir, sourceFile.getName()); + if (destFile.exists()) { + // plugin with same filename already installed + deleteQuietly(destFile); + } + + try { + if (deleteSource) { + moveFile(sourceFile, destFile); + } else { + copyFile(sourceFile, destFile, true); + } + } catch (IOException e) { + LOG.error(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()); + } + LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion()); + } else { + LOG.info("Plugin {} [{}] installed", info.getName(), info.getKey()); + } + } + + private void loadCorePlugins() { + for (File file : listJarFiles(fs.getCorePluginsDir())) { + PluginInfo info = PluginInfo.create(file).setCore(true); + registerPluginInfo(info); + } + } + + /** + * Removes the plugins that are not compatible with current environment. In some cases + * plugin files can be deleted. + */ + 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<>(); + do { + removedKeys.clear(); + for (PluginInfo plugin : pluginInfosByKeys.values()) { + 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()); + removedKeys.add(plugin.getKey()); + } + + if (!plugin.isCompatibleWith(server.getVersion())) { + LOG.warn("Plugin {} [{}] is ignored because it requires at least SonarQube {}", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()); + 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); + } + } while (!removedKeys.isEmpty()); + } + + private void logInstalledPlugins() { + List orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values()); + for (PluginInfo plugin : orderedPlugins) { + LOG.info("Plugin: {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild())); + } } - @CheckForNull - public ClassLoader getClassLoader(String pluginKey) { - return classloaders.get(pluginKey); + private void loadInstances() { + pluginInstancesByKeys.clear(); + pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys)); } - @CheckForNull - public Class getClass(String pluginKey, String classname) { - Class clazz = null; - ClassLoader classloader = getClassLoader(pluginKey); - if (classloader != null) { + /** + * Uninstall a plugin and its dependents (the plugins that require the plugin to be uninstalled) + */ + 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()); + } + } + } + } + + PluginInfo info = pluginInfosByKeys.get(pluginKey); + if (!info.isCore()) { try { - clazz = classloader.loadClass(classname); + // 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); + } + } + } + + public List getUninstalledPluginFilenames() { + return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), FileToName.INSTANCE)); + } - } catch (ClassNotFoundException e) { - Loggers.get(getClass()).warn("Class not found in plugin " + pluginKey + ": " + classname, e); + /** + * @return the list of plugins to be uninstalled as {@link PluginInfo} instances + */ + public Collection getUninstalledPlugins() { + return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), PluginInfo.JarToPluginInfo.INSTANCE)); + } + + public void cancelUninstalls() { + for (File file : listJarFiles(uninstalledPluginsDir())) { + try { + moveFileToDirectory(file, fs.getInstalledPluginsDir(), false); + } catch (IOException e) { + throw new IllegalStateException("Fail to cancel plugin uninstalls", e); } } - return clazz; + } + + public Map getPluginInfosByKeys() { + return pluginInfosByKeys; + } + + @Override + public Collection getPluginInfos() { + return pluginInfosByKeys.values(); + } + + @Override + public PluginInfo getPluginInfo(String key) { + PluginInfo info = pluginInfosByKeys.get(key); + if (info == null) { + throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key)); + } + return info; } @Override - public Collection getMetadata() { - return jarsInstaller.getMetadata(); + public Plugin getPluginInstance(String key) { + Plugin plugin = pluginInstancesByKeys.get(key); + if (plugin == null) { + throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key)); + } + return plugin; } @Override - public PluginMetadata getMetadata(String pluginKey) { - return jarsInstaller.getMetadata(pluginKey); + public boolean hasPlugin(String key) { + return pluginInfosByKeys.containsKey(key); } + private enum FileToName implements Function { + INSTANCE; + + @Override + public String apply(@Nonnull File file) { + return file.getName(); + } + + } + + /** + * @return existing trash dir + */ + private File uninstalledPluginsDir() { + File dir = new File(fs.getTempDir(), "uninstalled-plugins"); + try { + FileUtils.forceMkdir(dir); + return dir; + } catch (IOException e) { + throw new IllegalStateException("Fail to create temp directory: " + dir.getAbsolutePath(), e); + } + } + + private static Collection listJarFiles(File dir) { + if (dir.exists()) { + return FileUtils.listFiles(dir, new String[] {FILE_EXTENSION_JAR}, false); + } + return Collections.emptyList(); + } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java new file mode 100644 index 00000000000..59bc5a850fa --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java @@ -0,0 +1,65 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import org.apache.commons.io.FileUtils; +import org.sonar.api.ServerComponent; +import org.sonar.api.utils.ZipUtils; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginUnzipper; +import org.sonar.core.platform.UnzippedPlugin; +import org.sonar.server.platform.DefaultServerFileSystem; + +import java.io.File; + +import static org.apache.commons.io.FileUtils.cleanDirectory; +import static org.apache.commons.io.FileUtils.forceMkdir; + +public class ServerPluginUnzipper extends PluginUnzipper implements ServerComponent { + + private final DefaultServerFileSystem fs; + + public ServerPluginUnzipper(DefaultServerFileSystem fs) { + this.fs = fs; + } + + /** + * JAR files of directory extensions/plugins can be moved when server is up and plugins are uninstalled. + * For this reason these files must not be locked by classloaders. They are copied to the directory + * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}. + */ + @Override + public UnzippedPlugin unzip(PluginInfo pluginInfo) { + File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey()); + try { + forceMkdir(toDir); + cleanDirectory(toDir); + + File jarSource = pluginInfo.getFile(); + File jarTarget = new File(toDir, jarSource.getName()); + FileUtils.copyFile(jarSource, jarTarget); + ZipUtils.unzip(jarSource, toDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir); + } catch (Exception e) { + throw new IllegalStateException(String.format( + "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getFile().getAbsolutePath(), toDir.getAbsolutePath()), e); + } + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java index 8d24a1ae5f0..9a7ab9c3690 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java @@ -22,8 +22,10 @@ package org.sonar.server.plugins; import com.google.common.annotations.VisibleForTesting; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; +import org.sonar.api.Plugin; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginRepository; import org.sonar.server.platform.Platform; import javax.servlet.ServletException; @@ -45,29 +47,25 @@ public class StaticResourcesServlet extends HttpServlet { String pluginKey = getPluginKey(request); String resource = getResourcePath(request); - ServerPluginRepository pluginRepository = Platform.getInstance().getContainer().getComponentByType(ServerPluginRepository.class); - ClassLoader classLoader = pluginRepository.getClassLoader(pluginKey); - if (classLoader == null) { - LOG.error("Plugin not found: " + pluginKey); + PluginRepository pluginRepository = Platform.getInstance().getContainer().getComponentByType(PluginRepository.class); + if (!pluginRepository.hasPlugin(pluginKey)) { response.sendError(HttpServletResponse.SC_NOT_FOUND); return; } InputStream in = null; OutputStream out = null; try { - in = classLoader.getResourceAsStream(resource); + in = pluginRepository.getPluginInstance(pluginKey).getClass().getClassLoader().getResourceAsStream(resource); if (in != null) { // mime type must be set before writing response body completeContentType(response, resource); out = response.getOutputStream(); IOUtils.copy(in, out); - } else { - LOG.error("Unable to find resource '" + resource + "' in plugin '" + pluginKey + "'"); response.sendError(HttpServletResponse.SC_NOT_FOUND); } } catch (Exception e) { - LOG.error("Unable to load static resource '" + resource + "' from plugin '" + pluginKey + "'", e); + LOG.error(String.format("Unable to load resource [%s] from plugin [%s]", resource, pluginKey), e); response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR); } finally { IOUtils.closeQuietly(in); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java index 93124251589..3899e50985d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java @@ -24,17 +24,17 @@ import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.user.UserSession; public class CancelAllPluginsWsAction implements PluginsWsAction { private final PluginDownloader pluginDownloader; - private final ServerPluginJarsInstaller pluginJarsInstaller; + private final ServerPluginRepository pluginRepository; - public CancelAllPluginsWsAction(PluginDownloader pluginDownloader, ServerPluginJarsInstaller pluginJarsInstaller) { + public CancelAllPluginsWsAction(PluginDownloader pluginDownloader, ServerPluginRepository pluginRepository) { this.pluginDownloader = pluginDownloader; - this.pluginJarsInstaller = pluginJarsInstaller; + this.pluginRepository = pluginRepository; } @Override @@ -52,7 +52,7 @@ public class CancelAllPluginsWsAction implements PluginsWsAction { UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); pluginDownloader.cancelDownloads(); - pluginJarsInstaller.cancelUninstalls(); + pluginRepository.cancelUninstalls(); response.noContent(); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java index c84b730c96c..dc2f3ff4be7 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java @@ -22,14 +22,15 @@ package org.sonar.server.plugins.ws; import com.google.common.base.Predicate; import com.google.common.collect.ImmutableSortedSet; import com.google.common.io.Resources; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.plugins.ServerPluginRepository; import javax.annotation.Nullable; + import java.util.Collection; import java.util.SortedSet; @@ -42,10 +43,10 @@ import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADA public class InstalledPluginsWsAction implements PluginsWsAction { private static final String ARRAY_PLUGINS = "plugins"; - private final PluginRepository pluginRepository; + private final ServerPluginRepository pluginRepository; private final PluginWSCommons pluginWSCommons; - public InstalledPluginsWsAction(PluginRepository pluginRepository, + public InstalledPluginsWsAction(ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons) { this.pluginRepository = pluginRepository; this.pluginWSCommons = pluginWSCommons; @@ -62,39 +63,39 @@ public class InstalledPluginsWsAction implements PluginsWsAction { @Override public void handle(Request request, Response response) throws Exception { - Collection pluginMetadatas = retrieveAndSortPluginMetadata(); + Collection infos = retrieveAndSortPluginMetadata(); JsonWriter jsonWriter = response.newJsonWriter(); jsonWriter.setSerializeEmptys(false); jsonWriter.beginObject(); - writeMetadataList(jsonWriter, pluginMetadatas); + writeMetadataList(jsonWriter, infos); jsonWriter.endObject(); jsonWriter.close(); } - private SortedSet retrieveAndSortPluginMetadata() { + private SortedSet retrieveAndSortPluginMetadata() { return ImmutableSortedSet.copyOf( NAME_KEY_PLUGIN_METADATA_COMPARATOR, - filter(pluginRepository.getMetadata(), NotCorePluginsPredicate.INSTANCE) + filter(pluginRepository.getPluginInfos(), NotCorePluginsPredicate.INSTANCE) ); } - private void writeMetadataList(JsonWriter jsonWriter, Collection pluginMetadatas) { + private void writeMetadataList(JsonWriter jsonWriter, Collection pluginMetadatas) { jsonWriter.name(ARRAY_PLUGINS); jsonWriter.beginArray(); - for (PluginMetadata pluginMetadata : pluginMetadatas) { + for (PluginInfo pluginMetadata : pluginMetadatas) { pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata); } jsonWriter.endArray(); } - private enum NotCorePluginsPredicate implements Predicate { + private enum NotCorePluginsPredicate implements Predicate { INSTANCE; @Override - public boolean apply(@Nullable PluginMetadata input) { + public boolean apply(@Nullable PluginInfo input) { return input != null && !input.isCore(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java index 8c55caeaf68..29eb3cc9b53 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java @@ -19,14 +19,13 @@ */ package org.sonar.server.plugins.ws; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.api.utils.text.JsonWriter; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import java.util.Collection; @@ -43,24 +42,24 @@ public class PendingPluginsWsAction implements PluginsWsAction { private static final String ARRAY_REMOVING = "removing"; private final PluginDownloader pluginDownloader; - private final ServerPluginJarsInstaller serverPluginJarsInstaller; + private final ServerPluginRepository installer; private final PluginWSCommons pluginWSCommons; public PendingPluginsWsAction(PluginDownloader pluginDownloader, - ServerPluginJarsInstaller serverPluginJarsInstaller, - PluginWSCommons pluginWSCommons) { + ServerPluginRepository installer, + PluginWSCommons pluginWSCommons) { this.pluginDownloader = pluginDownloader; - this.serverPluginJarsInstaller = serverPluginJarsInstaller; + this.installer = installer; this.pluginWSCommons = pluginWSCommons; } @Override public void define(WebService.NewController controller) { controller.createAction("pending") - .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name") - .setSince("5.2") - .setHandler(this) - .setResponseExample(getResource(this.getClass(), "example-pending_plugins.json")); + .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name") + .setSince("5.2") + .setHandler(this) + .setResponseExample(getResource(this.getClass(), "example-pending_plugins.json")); } @Override @@ -80,8 +79,8 @@ public class PendingPluginsWsAction implements PluginsWsAction { private void writeInstalling(JsonWriter jsonWriter) { jsonWriter.name(ARRAY_INSTALLING); jsonWriter.beginArray(); - Collection plugins = pluginDownloader.getDownloadedPlugins(); - for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { + Collection plugins = pluginDownloader.getDownloadedPlugins(); + for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata); } jsonWriter.endArray(); @@ -90,8 +89,8 @@ public class PendingPluginsWsAction implements PluginsWsAction { private void writeRemoving(JsonWriter jsonWriter) { jsonWriter.name(ARRAY_REMOVING); jsonWriter.beginArray(); - Collection plugins = serverPluginJarsInstaller.getUninstalledPlugins(); - for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { + Collection plugins = installer.getUninstalledPlugins(); + for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata); } jsonWriter.endArray(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java index 08cc27555b4..c2f8a8b6b63 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java @@ -24,13 +24,14 @@ import com.google.common.base.Function; import com.google.common.collect.Ordering; import java.util.Comparator; import javax.annotation.Nonnull; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.utils.text.JsonWriter; +import org.sonar.core.platform.PluginInfo; import org.sonar.updatecenter.common.Artifact; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; import static com.google.common.collect.Iterables.filter; import static com.google.common.collect.Iterables.transform; @@ -59,7 +60,7 @@ public class PluginWSCommons { static final String PROPERTY_IMPLEMENTATION_BUILD = "implementationBuild"; static final String PROPERTY_CHANGE_LOG_URL = "changeLogUrl"; - public static final Ordering NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() + public static final Ordering NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() .onResultOf(PluginMetadataToName.INSTANCE) .compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE)); public static final Comparator NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) @@ -70,23 +71,26 @@ public class PluginWSCommons { public static final Comparator NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING) .onResultOf(PluginUpdateToPlugin.INSTANCE); - public void writePluginMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) { + public void writePluginMetadata(JsonWriter jsonWriter, PluginInfo info) { jsonWriter.beginObject(); - writeMetadata(jsonWriter, pluginMetadata); + writeMetadata(jsonWriter, info); jsonWriter.endObject(); } - public void writeMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) { + public void writeMetadata(JsonWriter jsonWriter, PluginInfo pluginMetadata) { jsonWriter.prop(PROPERTY_KEY, pluginMetadata.getKey()); jsonWriter.prop(PROPERTY_NAME, pluginMetadata.getName()); jsonWriter.prop(PROPERTY_DESCRIPTION, pluginMetadata.getDescription()); - jsonWriter.prop(PROPERTY_VERSION, pluginMetadata.getVersion()); + Version version = pluginMetadata.getVersion(); + if (version != null) { + jsonWriter.prop(PROPERTY_VERSION, version.getName()); + } jsonWriter.prop(PROPERTY_LICENSE, pluginMetadata.getLicense()); - jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, pluginMetadata.getOrganization()); + jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, pluginMetadata.getOrganizationName()); jsonWriter.prop(PROPERTY_ORGANIZATION_URL, pluginMetadata.getOrganizationUrl()); - jsonWriter.prop(PROPERTY_HOMEPAGE, pluginMetadata.getHomepage()); + jsonWriter.prop(PROPERTY_HOMEPAGE, pluginMetadata.getHomepageUrl()); jsonWriter.prop(PROPERTY_ISSUE_TRACKER_URL, pluginMetadata.getIssueTrackerUrl()); jsonWriter.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginMetadata.getImplementationBuild()); } @@ -221,20 +225,20 @@ public class PluginWSCommons { } } - private enum PluginMetadataToName implements Function { + private enum PluginMetadataToName implements Function { INSTANCE; @Override - public String apply(@Nonnull PluginMetadata input) { + public String apply(@Nonnull PluginInfo input) { return input.getName(); } } - private enum PluginMetadataToKey implements Function { + private enum PluginMetadataToKey implements Function { INSTANCE; @Override - public String apply(@Nonnull PluginMetadata input) { + public String apply(@Nonnull PluginInfo input) { return input.getKey(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java index bb2e9e1c43a..98399499acd 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java @@ -19,18 +19,13 @@ */ package org.sonar.server.plugins.ws; -import com.google.common.base.Predicate; -import javax.annotation.Nullable; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.Response; import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.user.UserSession; -import static com.google.common.collect.Iterables.find; import static java.lang.String.format; /** @@ -39,12 +34,10 @@ import static java.lang.String.format; public class UninstallPluginsWsAction implements PluginsWsAction { private static final String PARAM_KEY = "key"; - private final PluginRepository pluginRepository; - private final ServerPluginJarsInstaller pluginJarsInstaller; + private final ServerPluginRepository pluginRepository; - public UninstallPluginsWsAction(PluginRepository pluginRepository, ServerPluginJarsInstaller pluginJarsInstaller) { + public UninstallPluginsWsAction(ServerPluginRepository pluginRepository) { this.pluginRepository = pluginRepository; - this.pluginJarsInstaller = pluginJarsInstaller; } @Override @@ -66,27 +59,14 @@ public class UninstallPluginsWsAction implements PluginsWsAction { UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN); String key = request.mandatoryParam(PARAM_KEY); ensurePluginIsInstalled(key); - pluginJarsInstaller.uninstall(key); + pluginRepository.uninstall(key); response.noContent(); } + // FIXME should be moved to {@link ServerPluginRepository#uninstall(String)} private void ensurePluginIsInstalled(String key) { - if (find(pluginRepository.getMetadata(), new PluginKeyPredicate(key), null) == null) { - throw new IllegalArgumentException( - format("No plugin with key '%s' or plugin '%s' is not installed", key, key)); - } - } - - private static class PluginKeyPredicate implements Predicate { - private final String key; - - public PluginKeyPredicate(String key) { - this.key = key; - } - - @Override - public boolean apply(@Nullable PluginMetadata input) { - return input != null && key.equals(input.getKey()); + if (!pluginRepository.hasPlugin(key)) { + throw new IllegalArgumentException(format("Plugin [%s] is not installed", key)); } } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java b/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java index c29cd525cc9..d20d2c4e825 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java +++ b/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java @@ -29,7 +29,7 @@ import org.elasticsearch.action.delete.DeleteRequest; import org.elasticsearch.action.index.IndexRequest; import org.elasticsearch.action.update.UpdateRequest; import org.sonar.api.ServerComponent; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.core.cluster.WorkQueue; diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java index d79adb553c2..4fe91fb829f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java @@ -22,9 +22,9 @@ package org.sonar.server.startup; import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.CharUtils; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.api.ServerComponent; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import org.sonar.core.plugins.RemotePlugin; import org.sonar.server.platform.DefaultServerFileSystem; @@ -33,10 +33,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.Writer; -/** - * @since 2.11 - */ -public final class GeneratePluginIndex { +public final class GeneratePluginIndex implements ServerComponent { private DefaultServerFileSystem fileSystem; private PluginRepository repository; @@ -54,8 +51,8 @@ public final class GeneratePluginIndex { FileUtils.forceMkdir(indexFile.getParentFile()); Writer writer = new FileWriter(indexFile, false); try { - for (PluginMetadata metadata : repository.getMetadata()) { - writer.append(RemotePlugin.create((DefaultPluginMetadata) metadata).marshal()); + for (PluginInfo pluginInfo : repository.getPluginInfos()) { + writer.append(RemotePlugin.create(pluginInfo).marshal()); writer.append(CharUtils.LF); } writer.flush(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java index 4c2ed9deef1..71a39f1e1f9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java +++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java @@ -27,13 +27,12 @@ import java.util.List; import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; + +import org.sonar.api.Plugin; import org.sonar.api.config.License; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ComponentContainer; import org.sonar.api.platform.NewUserHandler; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.resources.Language; import org.sonar.api.resources.ResourceType; import org.sonar.api.resources.ResourceTypes; @@ -44,6 +43,9 @@ import org.sonar.api.web.RubyRailsWebservice; import org.sonar.api.web.Widget; import org.sonar.core.persistence.Database; import org.sonar.core.persistence.DatabaseVersion; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import org.sonar.core.resource.ResourceIndexerDao; import org.sonar.core.timemachine.Periods; import org.sonar.process.ProcessProperties; @@ -58,7 +60,6 @@ import org.sonar.server.platform.ServerSettings; import org.sonar.server.platform.SettingsChangeNotifier; import org.sonar.server.plugins.InstalledPluginReferentialFactory; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginJarsInstaller; import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.plugins.UpdateCenterMatrixFactory; import org.sonar.server.rule.RuleRepositories; @@ -149,15 +150,15 @@ public final class JRubyFacade { } public void uninstallPlugin(String pluginKey) { - get(ServerPluginJarsInstaller.class).uninstall(pluginKey); + get(ServerPluginRepository.class).uninstall(pluginKey); } public void cancelPluginUninstalls() { - get(ServerPluginJarsInstaller.class).cancelUninstalls(); + get(ServerPluginRepository.class).cancelUninstalls(); } public List getPluginUninstalls() { - return get(ServerPluginJarsInstaller.class).getUninstalledPluginFilenames(); + return get(ServerPluginRepository.class).getUninstalledPluginFilenames(); } public UpdateCenter getUpdatePluginCenter(boolean forceReload) { @@ -173,12 +174,11 @@ public final class JRubyFacade { return get(PropertyDefinitions.class); } - public boolean hasPlugin(String key) { - return get(PluginRepository.class).getPlugin(key) != null; - } - - public Collection getPluginsMetadata() { - return get(PluginRepository.class).getMetadata(); + /** + * Used for WS api/updatecenter/installed_plugins, to be replaced by api/plugins/installed. + */ + public Collection getPluginInfos() { + return get(PluginRepository.class).getPluginInfos(); } public List> getWidgets(String resourceScope, String resourceQualifier, String resourceLanguage, Object[] availableMeasures) { @@ -285,12 +285,13 @@ public final class JRubyFacade { } public Object getComponentByClassname(String pluginKey, String className) { - Object component = null; - Class componentClass = get(ServerPluginRepository.class).getClass(pluginKey, className); - if (componentClass != null) { - component = get(componentClass); + Plugin plugin = get(PluginRepository.class).getPluginInstance(pluginKey); + try { + Class componentClass = plugin.getClass().getClassLoader().loadClass(className); + return get(componentClass); + } catch (ClassNotFoundException e) { + throw new IllegalStateException(String.format("Class [%s] not found in plugin [%s]", className, pluginKey), e); } - return component; } private JRubyI18n getJRubyI18n() { @@ -391,7 +392,7 @@ public final class JRubyFacade { ComponentContainer container = Platform.getInstance().getContainer(); DatabaseMigration databaseMigration = container.getComponentByType(DatabaseMigration.class); if (databaseMigration.status() == DatabaseMigration.Status.RUNNING - || databaseMigration.status() == DatabaseMigration.Status.FAILED) { + || databaseMigration.status() == DatabaseMigration.Status.FAILED) { return false; } if (databaseMigration.status() == DatabaseMigration.Status.SUCCEEDED) { 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 942bb973c09..730584d829b 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 @@ -28,8 +28,8 @@ import org.junit.Test; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.io.FileInputStream; import java.io.InputStream; @@ -54,17 +54,15 @@ public class DebtModelPluginRepositoryTest { @Test public void test_component_initialization() throws Exception { // we do have the "csharp-model.xml" file in src/test/resources - PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class); - when(csharpPluginMetadata.getKey()).thenReturn("csharp"); + PluginInfo csharpPluginMetadata = new PluginInfo("csharp"); // but we don' have the "php-model.xml" one - PluginMetadata phpPluginMetadata = mock(PluginMetadata.class); - when(phpPluginMetadata.getKey()).thenReturn("php"); + PluginInfo phpPluginMetadata = new PluginInfo("php"); PluginRepository repository = mock(PluginRepository.class); - when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata)); + when(repository.getPluginInfos()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata)); FakePlugin fakePlugin = new FakePlugin(); - when(repository.getPlugin(anyString())).thenReturn(fakePlugin); + when(repository.getPluginInstance(anyString())).thenReturn(fakePlugin); modelFinder = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH); // when diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java index 5c49de4aef1..e8df89bb003 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java @@ -41,7 +41,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import org.elasticsearch.search.SearchHit; import org.junit.rules.ExternalResource; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.server.search.BaseDoc; import org.sonar.test.TestUtils; diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java index c06a7887022..57bf62c771a 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java @@ -37,20 +37,6 @@ public class DefaultServerFileSystemTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - @Test - public void find_plugins() throws Exception { - List plugins = new DefaultServerFileSystem( - new File(Resources.getResource(PATH + "shouldFindPlugins").toURI()), temp.newFolder(), null).getUserPlugins(); - assertThat(plugins).hasSize(2); - } - - @Test - public void not_fail_if_no_plugins() throws Exception { - List plugins = new DefaultServerFileSystem( - new File(Resources.getResource(PATH + "shouldNotFailIfNoPlugins").toURI()), temp.newFolder(), null).getUserPlugins(); - assertThat(plugins).isEmpty(); - } - @Test public void find_checkstyle_extensions() throws Exception { ServerFileSystem fs = new DefaultServerFileSystem( diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java index a64486ca9d9..d110225210d 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java @@ -23,9 +23,9 @@ import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.platform.ServerFileSystem; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.io.File; import java.net.URL; @@ -77,7 +77,7 @@ public class RailsAppsDeployerTest { when(fileSystem.getTempDir()).thenReturn(tempDir); PluginRepository pluginRepository = mock(PluginRepository.class); - when(pluginRepository.getMetadata()).thenReturn(Collections.emptyList()); + when(pluginRepository.getPluginInfos()).thenReturn(Collections.emptyList()); new RailsAppsDeployer(fileSystem, pluginRepository).start(); File appDir = new File(tempDir, "ror"); diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java deleted file mode 100644 index 66a4069fba1..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.platform; - -import org.junit.Test; - -public class SonarHomeTest { - @Test - public void iDontKnowHowToSimplyTestThisClass() { - - } -} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java index ef082c716b2..9e70d80a699 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java @@ -21,22 +21,22 @@ package org.sonar.server.platform.monitoring; import org.junit.Test; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; +import org.sonar.updatecenter.common.Version; -import java.util.ArrayList; -import java.util.Collection; +import java.util.Arrays; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; public class PluginsMonitorTest { - PluginsMonitor sut = new PluginsMonitor(new FakePluginRepository());; + PluginRepository repo = mock(PluginRepository.class); + PluginsMonitor sut = new PluginsMonitor(repo); @Test public void name() { @@ -44,42 +44,28 @@ public class PluginsMonitorTest { } @Test - public void plugin_name_and_version() { + public void plugin_name_and_version() throws Exception { + when(repo.getPluginInfos()).thenReturn(Arrays.asList( + new PluginInfo("key-1") + .setName("plugin-1") + .setVersion(Version.create("1.1")), + new PluginInfo("key-2") + .setName("plugin-2") + .setVersion(Version.create("2.2")), + new PluginInfo("no-version") + .setName("No Version"))); + LinkedHashMap attributes = sut.attributes(); assertThat(attributes).containsKeys("key-1", "key-2"); assertThat((Map) attributes.get("key-1")) .containsEntry("Name", "plugin-1") .containsEntry("Version", "1.1"); - assertThat((Map)attributes.get("key-2")) + assertThat((Map) attributes.get("key-2")) .containsEntry("Name", "plugin-2") .containsEntry("Version", "2.2"); - } - - private static class FakePluginRepository implements PluginRepository { - - @Override - public Plugin getPlugin(String key) { - return null; - } - - @Override - public Collection getMetadata() { - List plugins = new ArrayList<>(); - plugins.add(DefaultPluginMetadata - .create("key-1") - .setName("plugin-1") - .setVersion("1.1")); - plugins.add(DefaultPluginMetadata - .create("key-2") - .setName("plugin-2") - .setVersion("2.2")); - return plugins; - } - - @Override - public PluginMetadata getMetadata(String pluginKey) { - return null; - } + assertThat((Map) attributes.get("no-version")) + .containsEntry("Name", "No Version") + .doesNotContainKey("Version"); } } 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 40e1ada4f35..58c0e12c449 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 @@ -20,9 +20,8 @@ package org.sonar.server.plugins; import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; @@ -33,23 +32,22 @@ public class InstalledPluginReferentialFactoryTest { @Test public void should_create_plugin_referential() { - PluginMetadata metadata = mock(DefaultPluginMetadata.class); - when(metadata.getKey()).thenReturn("foo"); + PluginInfo info = new PluginInfo("foo"); PluginRepository pluginRepository = mock(PluginRepository.class); - when(pluginRepository.getMetadata()).thenReturn(newArrayList(metadata)); - InstalledPluginReferentialFactory installedPluginReferentialFactory = new InstalledPluginReferentialFactory(pluginRepository); + when(pluginRepository.getPluginInfos()).thenReturn(newArrayList(info)); + InstalledPluginReferentialFactory factory = new InstalledPluginReferentialFactory(pluginRepository); - assertThat(installedPluginReferentialFactory.getInstalledPluginReferential()).isNull(); - installedPluginReferentialFactory.start(); - assertThat(installedPluginReferentialFactory.getInstalledPluginReferential()).isNotNull(); - assertThat(installedPluginReferentialFactory.getInstalledPluginReferential().getPlugins()).hasSize(1); + assertThat(factory.getInstalledPluginReferential()).isNull(); + factory.start(); + assertThat(factory.getInstalledPluginReferential()).isNotNull(); + assertThat(factory.getInstalledPluginReferential().getPlugins()).hasSize(1); } @Test(expected = RuntimeException.class) public void should_encapsulate_exception() { PluginRepository pluginRepository = mock(PluginRepository.class); - when(pluginRepository.getMetadata()).thenThrow(new IllegalArgumentException()); - InstalledPluginReferentialFactory installedPluginReferentialFactory = new InstalledPluginReferentialFactory(pluginRepository); - installedPluginReferentialFactory.start(); + when(pluginRepository.getPluginInfos()).thenThrow(new IllegalArgumentException()); + InstalledPluginReferentialFactory factory = new InstalledPluginReferentialFactory(pluginRepository); + factory.start(); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java index 6c5f4412026..25b5b4978c4 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java @@ -20,6 +20,7 @@ package org.sonar.server.plugins; import org.junit.Test; +import org.sonar.test.TestUtils; import static org.assertj.core.api.Assertions.assertThat; @@ -35,4 +36,9 @@ public class MimeTypesTest { assertThat(MimeTypes.getByFilename("static/sqale/sqale.css")).isEqualTo("text/css"); assertThat(MimeTypes.getByFilename("sqale.css")).isEqualTo("text/css"); } + + @Test + public void only_statics() throws Exception { + assertThat(TestUtils.hasOnlyPrivateConstructors(MimeTypes.class)).isTrue(); + } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java index bf460fffd2b..a5178f40383 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java @@ -29,11 +29,12 @@ import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; import org.sonar.api.utils.HttpDownloader; import org.sonar.api.utils.SonarException; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.server.platform.DefaultServerFileSystem; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.Release; import org.sonar.updatecenter.common.UpdateCenter; +import org.sonar.updatecenter.common.Version; import java.io.File; import java.net.URI; @@ -66,7 +67,6 @@ public class PluginDownloaderTest { UpdateCenter updateCenter; HttpDownloader httpDownloader; PluginDownloader pluginDownloader; - ServerPluginJarInstaller installer; @Before public void before() throws Exception { @@ -88,8 +88,7 @@ public class PluginDownloaderTest { downloadDir = testFolder.newFolder("downloads"); when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir); - installer = new ServerPluginJarInstaller(); - pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem, installer); + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem); } @After @@ -155,7 +154,7 @@ public class PluginDownloaderTest { File downloadDir = testFolder.newFile(); when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir); - pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem, installer); + pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem); try { pluginDownloader.start(); fail(); @@ -220,26 +219,18 @@ public class PluginDownloaderTest { pluginDownloader.start(); assertThat(pluginDownloader.getDownloadedPluginFilenames()).hasSize(0); - copyFileToDirectory(new File(resource("foo-plugin-1.0.jar")), downloadDir); + copyFileToDirectory(TestProjectUtils.jarOf("test-base-plugin"), downloadDir); assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(1); - DefaultPluginMetadata metadata = pluginDownloader.getDownloadedPlugins().iterator().next(); - assertThat(metadata.getKey()).isEqualTo("foo"); - assertThat(metadata.getName()).isEqualTo("Foo"); - assertThat(metadata.getVersion()).isEqualTo("1.0"); - assertThat(metadata.getOrganization()).isEqualTo("SonarSource"); - assertThat(metadata.getOrganizationUrl()).isEqualTo("http://www.sonarsource.org"); - assertThat(metadata.getLicense()).isEqualTo("LGPL 3"); - assertThat(metadata.getMainClass()).isEqualTo("foo.Main"); - } - - private URI resource(String fileName) throws URISyntaxException { - URL resource = getClass().getResource(getClass().getSimpleName() + "/" + fileName); - return resource.toURI(); + PluginInfo info = pluginDownloader.getDownloadedPlugins().iterator().next(); + assertThat(info.getKey()).isEqualTo("testbase"); + assertThat(info.getName()).isEqualTo("Base Plugin"); + assertThat(info.getVersion()).isEqualTo(Version.create("0.1-SNAPSHOT")); + assertThat(info.getMainClass()).isEqualTo("BasePlugin"); } @Test - public void getDownloadedPluginFilenames_reads_plugin_metadata_of_files_in_download_folder() throws Exception { + public void getDownloadedPluginFilenames_reads_plugin_info_of_files_in_download_folder() throws Exception { pluginDownloader.start(); assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(0); 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 a7cc85c9694..c7891203caa 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 @@ -20,23 +20,19 @@ package org.sonar.server.plugins; import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.updatecenter.common.PluginReferential; import static com.google.common.collect.Lists.newArrayList; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; public class PluginReferentialMetadataConverterTest { @Test - public void should_convert_metadata_to_plugin_referential() { - PluginMetadata metadata = mock(DefaultPluginMetadata.class); - when(metadata.getKey()).thenReturn("foo"); + public void should_convert_info_to_plugin_referential() { + PluginInfo info = new PluginInfo("foo"); - PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(metadata)); + PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info)); assertThat(pluginReferential).isNotNull(); assertThat(pluginReferential.getPlugins()).hasSize(1); assertThat(pluginReferential.getPlugins().get(0).getKey()).isEqualTo("foo"); @@ -44,11 +40,9 @@ public class PluginReferentialMetadataConverterTest { @Test public void should_not_add_core_plugin() { - PluginMetadata metadata = mock(DefaultPluginMetadata.class); - when(metadata.getKey()).thenReturn("foo"); - when(metadata.isCore()).thenReturn(true); + PluginInfo info = new PluginInfo("foo").setCore(true); - PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(metadata)); + PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info)); assertThat(pluginReferential).isNotNull(); assertThat(pluginReferential.getPlugins()).hasSize(0); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java deleted file mode 100644 index 8edde701e28..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java +++ /dev/null @@ -1,309 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.server.plugins; - -import com.google.common.io.Resources; -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.Server; -import org.sonar.api.platform.ServerUpgradeStatus; -import org.sonar.api.utils.MessageException; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.server.platform.DefaultServerFileSystem; - -import java.io.File; -import java.util.Collection; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class ServerPluginJarsInstallerTest { - - @Rule - public ExpectedException exception = ExpectedException.none(); - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - DefaultServerFileSystem fileSystem; - File homeDir, pluginsDir, downloadsDir, bundledDir, trashDir, coreDir; - ServerPluginJarInstaller jarInstaller; - ServerPluginJarsInstaller jarsInstaller; - Server server = mock(Server.class); - ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class); - - @Before - public void before() throws Exception { - when(server.getVersion()).thenReturn("3.1"); - when(server.getDeployDir()).thenReturn(temp.newFolder("deploy")); - - homeDir = temp.newFolder("home"); - pluginsDir = new File(homeDir, "extensions/plugins"); - FileUtils.forceMkdir(pluginsDir); - downloadsDir = new File(homeDir, "extensions/downloads"); - trashDir = new File(homeDir, "extensions/trash"); - bundledDir = new File(homeDir, "lib/bundled-plugins"); - coreDir = new File(homeDir, "lib/core-plugins"); - FileUtils.forceMkdir(bundledDir); - - fileSystem = new DefaultServerFileSystem(homeDir, temp.newFolder(), server); - jarInstaller = new ServerPluginJarInstaller(); - jarsInstaller = new ServerPluginJarsInstaller(server, upgradeStatus, fileSystem, jarInstaller); - } - - private File jar(String name) throws Exception { - return new File(Resources.getResource(getClass(), "ServerPluginJarsInstallerTest/" + name).toURI()); - } - - @Test - public void copy_bundled_plugin_on_fresh_install() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(true); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir); - - jarsInstaller.install(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); - PluginMetadata plugin = jarsInstaller.getMetadata("foo"); - assertThat(plugin.getName()).isEqualTo("Foo"); - assertThat(plugin.getDeployedFiles()).hasSize(1); - assertThat(plugin.isCore()).isFalse(); - assertThat(plugin.isUseChildFirstClassLoader()).isFalse(); - } - - @Test - public void do_not_copy_bundled_plugin_on_non_fresh_install() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir); - - jarsInstaller.install(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty(); - } - - @Test - public void do_not_copy_bundled_plugin_if_already_installed() throws Exception { - // fresh install but plugins are already packaged in extensions/plugins - when(upgradeStatus.isFreshInstall()).thenReturn(true); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir); - FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), pluginsDir); - FileUtils.copyFileToDirectory(jar("bar-plugin-1.0.jar"), pluginsDir); - - jarsInstaller.install(); - - // do not copy foo 1.0 - assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(2); - assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile(); - assertThat(new File(pluginsDir, "bar-plugin-1.0.jar")).exists().isFile(); - PluginMetadata plugin = jarsInstaller.getMetadata("foo"); - assertThat(plugin.getVersion()).isEqualTo("2.0"); - } - - @Test - public void deploy_installed_plugin() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - - jarsInstaller.install(); - - // check that the plugin is registered - assertThat(jarsInstaller.getMetadata()).hasSize(1); - PluginMetadata plugin = jarsInstaller.getMetadata("foo"); - assertThat(plugin.getName()).isEqualTo("Foo"); - assertThat(plugin.getDeployedFiles()).hasSize(1); - assertThat(plugin.isCore()).isFalse(); - assertThat(plugin.isUseChildFirstClassLoader()).isFalse(); - - // check that the file is still present in extensions/plugins - assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); - assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); - } - - @Test - public void ignore_non_plugin_jars() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("not-a-plugin.jar"), pluginsDir); - - jarsInstaller.install(); - - // nothing to install but keep the file - assertThat(jarsInstaller.getMetadata()).isEmpty(); - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(new File(pluginsDir, "not-a-plugin.jar")).exists().isFile(); - } - - @Test - public void fail_if_plugin_requires_greater_SQ_version() throws Exception { - exception.expect(MessageException.class); - exception.expectMessage("Plugin switchoffviolations needs a more recent version of SonarQube than 2.0. At least 2.5 is expected"); - - when(upgradeStatus.isFreshInstall()).thenReturn(false); - when(server.getVersion()).thenReturn("2.0"); - FileUtils.copyFileToDirectory(jar("require-sq-2.5.jar"), pluginsDir); - - jarsInstaller.install(); - } - - @Test - public void move_downloaded_plugins() throws Exception { - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), downloadsDir); - when(upgradeStatus.isFreshInstall()).thenReturn(false); - - jarsInstaller.install(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1); - assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); - assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile(); - } - - @Test - public void downloaded_plugins_overrides_existing_plugin() throws Exception { - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), downloadsDir); - when(upgradeStatus.isFreshInstall()).thenReturn(false); - - jarsInstaller.install(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); - assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile(); - } - - @Test - public void downloaded_plugins_overrides_existing_plugin_even_if_same_filename() throws Exception { - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir, true); - // foo-plugin-1.0.jar in extensions/downloads is in fact version 2.0. It's used to verify - // that it has correctly overridden extensions/plugins/foo-plugin-1.0.jar - FileUtils.copyFile(jar("foo-plugin-2.0.jar"), new File(downloadsDir, "foo-plugin-1.0.jar")); - when(upgradeStatus.isFreshInstall()).thenReturn(false); - - jarsInstaller.install(); - - PluginMetadata plugin = jarsInstaller.getMetadata("foo"); - assertThat(plugin).isNotNull(); - assertThat(plugin.getVersion()).isEqualTo("2.0"); - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty(); - File installed = new File(pluginsDir, "foo-plugin-1.0.jar"); - assertThat(installed).exists().isFile(); - } - - @Test - public void delete_trash() throws Exception { - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), trashDir); - when(upgradeStatus.isFreshInstall()).thenReturn(false); - - jarsInstaller.install(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty(); - assertThat(trashDir).doesNotExist(); - } - - @Test - public void fail_if_two_installed_plugins_with_same_key() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), pluginsDir); - - try { - jarsInstaller.install(); - fail(); - } catch (MessageException e) { - assertThat(e.getMessage()) - .contains("Found two files for the same plugin 'foo'") - .contains("foo-plugin-1.0.jar") - .contains("foo-plugin-2.0.jar"); - } - } - - @Test - public void uninstall_plugin() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - - jarsInstaller.install(); - jarsInstaller.uninstall("foo"); - - assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty(); - assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(jarsInstaller.getUninstalledPluginFilenames()).containsOnly("foo-plugin-1.0.jar"); - } - - @Test - public void pending_removals_reads_metadata() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - - jarsInstaller.install(); - jarsInstaller.uninstall("foo"); - - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty(); - assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(1); - Collection removals = jarsInstaller.getUninstalledPlugins(); - assertThat(removals).hasSize(1); - PluginMetadata metadata = removals.iterator().next(); - assertThat(metadata.getKey()).isEqualTo("foo"); - assertThat(metadata.getName()).isEqualTo("Foo"); - assertThat(metadata.getVersion()).isEqualTo("1.0"); - assertThat(metadata.getOrganization()).isEqualTo("SonarSource"); - assertThat(metadata.getOrganizationUrl()).isEqualTo("http://www.sonarsource.org"); - assertThat(metadata.getLicense()).isEqualTo("LGPL 3"); - assertThat(metadata.getMainClass()).isEqualTo("foo.Main"); - } - - @Test - public void cancel_uninstallation() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir); - - jarsInstaller.install(); - jarsInstaller.uninstall("foo"); - jarsInstaller.cancelUninstalls(); - - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1); - assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(0); - assertThat(jarsInstaller.getUninstalledPluginFilenames()).isEmpty(); - } - - @Test - public void deploy_core_plugins() throws Exception { - when(upgradeStatus.isFreshInstall()).thenReturn(false); - FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), coreDir); - - jarsInstaller.install(); - - // do not deploy in extensions/plugins - assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(0); - - // do not remove from lib/core-plugins - assertThat(FileUtils.listFiles(coreDir, new String[] {"jar"}, false)).hasSize(1); - - PluginMetadata plugin = jarsInstaller.getMetadata("foo"); - assertThat(plugin).isNotNull(); - assertThat(plugin.isCore()).isTrue(); - } -} 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 5f1898dae31..ea87cb3af88 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,47 +19,298 @@ */ package org.sonar.server.plugins; -import com.google.common.io.Resources; +import com.google.common.collect.ImmutableSet; +import org.apache.commons.io.FileUtils; import org.junit.After; +import org.junit.Before; +import org.junit.Rule; import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.junit.rules.TemporaryFolder; +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.core.platform.PluginLoader; +import org.sonar.server.platform.DefaultServerFileSystem; +import org.sonar.updatecenter.common.Version; import java.io.File; -import java.util.Arrays; +import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; public class ServerPluginRepositoryTest { - ServerPluginRepository repository; + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + 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)); + ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader); + + @Before + public void setUp() throws IOException { + when(fs.getBundledPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getCorePluginsDir()).thenReturn(temp.newFolder()); + when(fs.getDeployedPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getDownloadedPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getHomeDir()).thenReturn(temp.newFolder()); + when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder()); + when(fs.getTempDir()).thenReturn(temp.newFolder()); + when(server.getVersion()).thenReturn("5.2"); + } @After - public void stop() { - if (repository != null) { - repository.stop(); + public void tearDown() throws Exception { + underTest.stop(); + } + + /** + * The first server startup (fresh db) installs bundled plugins and instantiates bundled + core plugins. + */ + @Test + public void first_startup_installs_bundled_plugins() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getBundledPluginsDir()); + copyTestPluginTo("test-core-plugin", fs.getCorePluginsDir()); + when(upgradeStatus.isFreshInstall()).thenReturn(true); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("core", "testbase"); + assertThat(underTest.getPluginInstance("core").getClass().getName()).isEqualTo("CorePlugin"); + assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin"); + assertThat(underTest.hasPlugin("testbase")).isTrue(); + } + + @Test + public void bundled_plugins_are_not_installed_if_not_fresh_server() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getBundledPluginsDir()); + when(upgradeStatus.isFreshInstall()).thenReturn(false); + + underTest.start(); + + assertThat(underTest.getPluginInfos()).isEmpty(); + } + + @Test + public void standard_startup_loads_core_and_installed_plugins() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-core-plugin", fs.getCorePluginsDir()); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("core", "testbase"); + assertThat(underTest.getPluginInstance("core").getClass().getName()).isEqualTo("CorePlugin"); + assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin"); + } + + /** + * That sounds impossible, there are still core plugins for now, but it's still valuable + * to test sensibility to null values. + */ + @Test + public void no_plugins_at_all_on_startup() throws Exception { + underTest.start(); + + assertThat(underTest.getPluginInfos()).isEmpty(); + assertThat(underTest.getPluginInfosByKeys()).isEmpty(); + assertThat(underTest.getUninstalledPlugins()).isEmpty(); + assertThat(underTest.hasPlugin("testbase")).isFalse(); + } + + @Test + public void fail_if_multiple_jars_for_same_installed_plugin_on_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-base-plugin-v2", fs.getInstalledPluginsDir()); + + try { + underTest.start(); + fail(); + } catch (MessageException e) { + assertThat(e) + .hasMessageStartingWith("Found two files for the same plugin [testbase]: ") + // order is not guaranteed, so assertion is split + .hasMessageContaining("test-base-plugin-0.1-SNAPSHOT.jar") + .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar"); } } @Test - public void testStart() throws Exception { - ServerPluginJarsInstaller deployer = mock(ServerPluginJarsInstaller.class); - File pluginFile = new File(Resources.getResource("org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar").toURI()); - PluginMetadata plugin = DefaultPluginMetadata.create(pluginFile) - .setKey("artifactsize") - .setMainClass("org.sonar.plugins.artifactsize.ArtifactSizePlugin") - .addDeployedFile(pluginFile); - when(deployer.getMetadata()).thenReturn(Arrays.asList(plugin)); + public void install_downloaded_plugins_on_startup() throws Exception { + File downloadedJar = copyTestPluginTo("test-base-plugin", fs.getDownloadedPluginsDir()); + + underTest.start(); + + // plugin is moved to extensions/plugins then loaded + assertThat(downloadedJar).doesNotExist(); + assertThat(new File(fs.getInstalledPluginsDir(), downloadedJar.getName())).isFile().exists(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin"); + } + + @Test + public void downloaded_file_overrides_existing_installed_file_on_startup() throws Exception { + File installedV1 = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File downloadedV2 = copyTestPluginTo("test-base-plugin-v2", fs.getDownloadedPluginsDir()); + + underTest.start(); + + // plugin is moved to extensions/plugins and replaces v1 + assertThat(downloadedV2).doesNotExist(); + assertThat(installedV1).doesNotExist(); + assertThat(new File(fs.getInstalledPluginsDir(), downloadedV2.getName())).exists(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + assertThat(underTest.getPluginInfo("testbase").getVersion()).isEqualTo(Version.create("0.2-SNAPSHOT")); + } + + @Test + public void blacklisted_plugin_is_automatically_uninstalled_on_startup() throws Exception { + underTest.setBlacklistedPluginKeys(ImmutableSet.of("testbase", "issuesreport")); + File jar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); - repository = new ServerPluginRepository(deployer); - repository.start(); + // plugin is not installed and file is deleted + assertThat(underTest.getPluginInfos()).isEmpty(); + assertThat(jar).doesNotExist(); + } + + @Test + public void test_plugin_requirements_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testrequire"); + } + + @Test + public void plugin_is_ignored_if_required_plugin_is_missing_at_startup() throws Exception { + copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin is not installed as test-base-plugin is missing + assertThat(underTest.getPluginInfosByKeys()).isEmpty(); + } + + @Test + public void plugin_is_ignored_if_required_plugin_is_too_old_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-requirenew-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // the plugin "requirenew" is not installed as it requires base 0.2+ to be installed. + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + } + + @Test + public void plugin_is_ignored_at_startup_if_unsupported_sq() throws Exception { + when(server.getVersion()).thenReturn("1.0"); + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin requires SQ 4.5.1 but SQ 1.0 is installed + assertThat(underTest.getPluginInfos()).isEmpty(); + } + + @Test + public void uninstall() throws Exception { + File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + underTest.uninstall("testbase"); + + assertThat(installedJar).doesNotExist(); + // still up. Will be dropped after next startup + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase"); + assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(installedJar.getName()); + assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase"); + } + + @Test + public void uninstall_dependents() throws Exception { + File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + assertThat(underTest.getPluginInfos()).hasSize(2); + underTest.uninstall("testbase"); + + assertThat(base).doesNotExist(); + assertThat(extension).doesNotExist(); + assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(base.getName(), extension.getName()); + assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase", "testrequire"); + } + + @Test + public void cancel_uninstall() throws Exception { + File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + underTest.start(); + + underTest.uninstall("testbase"); + assertThat(base).doesNotExist(); + + underTest.cancelUninstalls(); + assertThat(base).exists(); + assertThat(underTest.getUninstalledPluginFilenames()).isEmpty(); + assertThat(underTest.getUninstalledPlugins()).isEmpty(); + } + + @Test + public void install_plugin_and_its_extension_plugins_at_startup() throws Exception { + copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir()); + copyTestPluginTo("test-extend-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // both plugins are installed + assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testextend"); + } + + @Test + public void extension_plugin_is_ignored_if_base_plugin_is_missing_at_startup() throws Exception { + copyTestPluginTo("test-extend-plugin", fs.getInstalledPluginsDir()); + + underTest.start(); + + // plugin is not installed as its base plugin is not installed + assertThat(underTest.getPluginInfos()).isEmpty(); + } + + @Test + public void fail_is_missing_required_plugin() throws Exception { + try { + underTest.getPluginInfo("unknown"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + + try { + underTest.getPluginInstance("unknown"); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e).hasMessage("Plugin [unknown] does not exist"); + } + } - assertThat(repository.getPlugin("artifactsize")).isNotNull(); - assertThat(repository.getClassLoader("artifactsize")).isNotNull(); - assertThat(repository.getClass("artifactsize", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics")).isNotNull(); - assertThat(repository.getClass("artifactsize", "org.Unknown")).isNull(); - assertThat(repository.getClass("other", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics")).isNull(); + 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 + FileUtils.copyFileToDirectory(jar, toDir); + return new File(toDir, jar.getName()); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java new file mode 100644 index 00000000000..efea25d39e8 --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java @@ -0,0 +1,64 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.UnzippedPlugin; +import org.sonar.server.platform.DefaultServerFileSystem; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class ServerPluginUnzipperTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class); + ServerPluginUnzipper underTest = new ServerPluginUnzipper(fs); + + @Test + public void copy_all_classloader_files_to_dedicated_directory() throws Exception { + File deployDir = temp.newFolder(); + when(fs.getDeployedPluginsDir()).thenReturn(deployDir); + File jar = TestProjectUtils.jarOf("test-libs-plugin"); + PluginInfo info = PluginInfo.create(jar); + + UnzippedPlugin unzipped = underTest.unzip(info); + + // all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory + // web/deploy/{pluginKey} + File pluginDeployDir = new File(deployDir, "testlibs"); + + assertThat(unzipped.getKey()).isEqualTo("testlibs"); + assertThat(unzipped.getMain()).isFile().exists().hasParent(pluginDeployDir); + assertThat(unzipped.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar"); + for (File lib : unzipped.getLibs()) { + assertThat(lib).exists().isFile(); + assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath()); + } + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java new file mode 100644 index 00000000000..00d579118ee --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java @@ -0,0 +1,40 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.server.plugins; + +import org.apache.commons.io.FileUtils; + +import java.io.File; +import java.util.Collection; + +public class TestProjectUtils { + + /** + * Get the artifact of plugins stored in src/test/projects + */ + public static File jarOf(String dirName) { + File target = FileUtils.toFile(TestProjectUtils.class.getResource(String.format("/%s/target/", dirName))); + Collection jars = FileUtils.listFiles(target, new String[] {"jar"}, false); + if (jars == null || jars.size() != 1) { + throw new IllegalArgumentException("Test project is badly defined: " + dirName); + } + return jars.iterator().next(); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java index 4ff9f43f340..3d2260eba4f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java @@ -28,7 +28,7 @@ import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; import org.sonar.server.exceptions.ForbiddenException; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; @@ -41,8 +41,8 @@ public class CancelAllPluginsWsActionTest { private static final String DUMMY_CONTROLLER_KEY = "dummy"; private PluginDownloader pluginDownloader = mock(PluginDownloader.class); - private ServerPluginJarsInstaller pluginJarsInstaller = mock(ServerPluginJarsInstaller.class); - private CancelAllPluginsWsAction underTest = new CancelAllPluginsWsAction(pluginDownloader, pluginJarsInstaller); + private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); + private CancelAllPluginsWsAction underTest = new CancelAllPluginsWsAction(pluginDownloader, pluginRepository); private Request request = mock(Request.class); private WsTester.TestResponse response = new WsTester.TestResponse(); @@ -90,7 +90,7 @@ public class CancelAllPluginsWsActionTest { underTest.handle(request, response); verify(pluginDownloader, times(1)).cancelDownloads(); - verify(pluginJarsInstaller, times(1)).cancelUninstalls(); + verify(pluginRepository, times(1)).cancelUninstalls(); assertThat(response.outputAsString()).isEmpty(); } 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 3780d88af00..11a544e1d23 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,17 +19,17 @@ */ package org.sonar.server.plugins.ws; -import java.io.File; import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.ws.WsTester; +import org.sonar.updatecenter.common.Version; + +import java.io.File; import static com.google.common.collect.ImmutableList.of; -import static java.lang.String.valueOf; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -42,12 +42,12 @@ public class InstalledPluginsWsActionTest { " \"plugins\":" + "[]" + "}"; - private PluginRepository pluginRepository = mock(PluginRepository.class); + 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 PluginMetadata corePlugin = corePlugin("core1", 10); + private PluginInfo corePlugin = corePlugin("core1", "1.0"); @Test public void action_installed_is_defined() { @@ -75,7 +75,7 @@ public class InstalledPluginsWsActionTest { @Test public void core_plugin_are_not_returned() throws Exception { - when(pluginRepository.getMetadata()).thenReturn(of(corePlugin)); + when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin)); underTest.handle(request, response); @@ -84,9 +84,9 @@ public class InstalledPluginsWsActionTest { @Test public void empty_fields_are_not_serialized_to_json() throws Exception { - when(pluginRepository.getMetadata()).thenReturn( + when(pluginRepository.getPluginInfos()).thenReturn( of( - (PluginMetadata) DefaultPluginMetadata.create("").setName("").setCore(false) + new PluginInfo("").setName("").setCore(false) ) ); @@ -98,14 +98,14 @@ public class InstalledPluginsWsActionTest { @Test public void verify_properties_displayed_in_json_per_plugin() throws Exception { String jarFilename = getClass().getSimpleName() + "/" + "some.jar"; - when(pluginRepository.getMetadata()).thenReturn(of( - (PluginMetadata) DefaultPluginMetadata.create("plugKey").setName("plugName").setCore(false) + when(pluginRepository.getPluginInfos()).thenReturn(of( + new PluginInfo("plugKey").setName("plugName").setCore(false) .setDescription("desc_it") - .setVersion(valueOf(10)) + .setVersion(Version.create("1.0")) .setLicense("license_hey") - .setOrganization("org_name") + .setOrganizationName("org_name") .setOrganizationUrl("org_url") - .setHomepage("homepage_url") + .setHomepageUrl("homepage_url") .setIssueTrackerUrl("issueTracker_url") .setFile(new File(getClass().getResource(jarFilename).toURI())) .setImplementationBuild("sou_rev_sha1") @@ -122,7 +122,7 @@ public class InstalledPluginsWsActionTest { " \"key\": \"plugKey\"," + " \"name\": \"plugName\"," + " \"description\": \"desc_it\"," + - " \"version\": \"10\"," + + " \"version\": \"1.0\"," + " \"license\": \"license_hey\"," + " \"organizationName\": \"org_name\"," + " \"organizationUrl\": \"org_url\"," + @@ -137,7 +137,7 @@ public class InstalledPluginsWsActionTest { @Test public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws Exception { - when(pluginRepository.getMetadata()).thenReturn( + when(pluginRepository.getPluginInfos()).thenReturn( of( plugin("A", "name2"), plugin("B", "name1"), @@ -163,7 +163,7 @@ public class InstalledPluginsWsActionTest { @Test public void only_one_plugin_can_have_a_specific_name_and_key() throws Exception { - when(pluginRepository.getMetadata()).thenReturn( + when(pluginRepository.getPluginInfos()).thenReturn( of( plugin("A", "name2"), plugin("A", "name2") @@ -183,15 +183,15 @@ public class InstalledPluginsWsActionTest { assertThat(response.outputAsString()).containsOnlyOnce("name2"); } - private static PluginMetadata corePlugin(String key, int version) { - return DefaultPluginMetadata.create(key).setName(key).setCore(true).setVersion(valueOf(version)); + private static PluginInfo corePlugin(String key, String version) { + return new PluginInfo(key).setName(key).setCore(true).setVersion(Version.create(version)); } - private static PluginMetadata plugin(String key, String name, int version) { - return DefaultPluginMetadata.create(key).setName(name).setCore(false).setVersion(valueOf(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 PluginMetadata plugin(String key, String name) { - return DefaultPluginMetadata.create(key).setName(name).setCore(false).setVersion("1.0"); + private static 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 558ed57839a..1849151312a 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 @@ -23,39 +23,39 @@ import java.io.File; import org.junit.Test; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.server.plugins.PluginDownloader; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.ws.WsTester; +import org.sonar.updatecenter.common.Version; import static com.google.common.collect.ImmutableList.of; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.sonar.core.plugins.DefaultPluginMetadata.create; import static org.sonar.test.JsonAssert.assertJson; public class PendingPluginsWsActionTest { - public static final DefaultPluginMetadata GIT_PLUGIN_METADATA = create("scmgit") + public static final PluginInfo GIT_PLUGIN_INFO = new PluginInfo("scmgit") .setName("Git") .setDescription("Git SCM Provider.") - .setVersion("1.0") + .setVersion(Version.create("1.0")) .setLicense("GNU LGPL 3") - .setOrganization("SonarSource") + .setOrganizationName("SonarSource") .setOrganizationUrl("http://www.sonarsource.com") - .setHomepage("http://redirect.sonarsource.com/plugins/scmgit.html") + .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 DefaultPluginMetadata PLUGIN_2_2 = create("key2").setName("name2"); - public static final DefaultPluginMetadata PLUGIN_2_1 = create("key1").setName("name2"); - public static final DefaultPluginMetadata PLUGIN_0_0 = create("key0").setName("name0"); + 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 ServerPluginJarsInstaller serverPluginJarsInstaller = mock(ServerPluginJarsInstaller.class); - private PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginJarsInstaller, new PluginWSCommons()); + 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(); @@ -91,7 +91,7 @@ public class PendingPluginsWsActionTest { @Test public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception { - when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_METADATA)); + when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_INFO)); underTest.handle(request, response); @@ -119,7 +119,7 @@ public class PendingPluginsWsActionTest { @Test public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception { - when(serverPluginJarsInstaller.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_METADATA)); + when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_INFO)); underTest.handle(request, response); @@ -180,7 +180,7 @@ public class PendingPluginsWsActionTest { @Test public void removing_plugin_are_sorted_and_unique() throws Exception { - when(serverPluginJarsInstaller.getUninstalledPlugins()).thenReturn(of( + when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of( PLUGIN_2_2, PLUGIN_2_1, PLUGIN_2_2, 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 831bdbd7b78..e055635b343 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 @@ -22,7 +22,7 @@ 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.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; import org.sonar.server.ws.WsTester; import org.sonar.updatecenter.common.Plugin; import org.sonar.updatecenter.common.PluginUpdate; @@ -31,7 +31,6 @@ import org.sonar.updatecenter.common.Version; import static org.assertj.core.api.Assertions.assertThat; import static org.sonar.api.utils.DateUtils.parseDate; -import static org.sonar.core.plugins.DefaultPluginMetadata.create; import static org.sonar.server.plugins.ws.PluginWSCommons.toJSon; import static org.sonar.test.JsonAssert.assertJson; import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE; @@ -40,14 +39,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 DefaultPluginMetadata GIT_PLUGIN_METADATA = create("scmgit") + private static final PluginInfo GIT_PLUGIN_METADATA = new PluginInfo("scmgit") .setName("Git") .setDescription("Git SCM Provider.") - .setVersion("1.0") + .setVersion(Version.create("1.0")) .setLicense("GNU LGPL 3") - .setOrganization("SonarSource") + .setOrganizationName("SonarSource") .setOrganizationUrl("http://www.sonarsource.com") - .setHomepage("http://redirect.sonarsource.com/plugins/scmgit.html") + .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") @@ -202,7 +201,7 @@ public class PluginWSCommonsTest { PluginUpdate pluginUpdate = new PluginUpdate(); pluginUpdate.setRelease( new Release(PLUGIN, version("1.0")).addOutgoingDependency(RELEASE) - ); + ); jsonWriter.beginObject(); underTest.writeUpdate(jsonWriter, pluginUpdate); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java index fa8adaa0f98..85a3bba714f 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java @@ -19,19 +19,15 @@ */ package org.sonar.server.plugins.ws; -import com.google.common.collect.ImmutableList; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.server.ws.Request; import org.sonar.api.server.ws.WebService; import org.sonar.core.permission.GlobalPermissions; -import org.sonar.core.plugins.DefaultPluginMetadata; import org.sonar.server.exceptions.ForbiddenException; -import org.sonar.server.plugins.ServerPluginJarsInstaller; +import org.sonar.server.plugins.ServerPluginRepository; import org.sonar.server.user.MockUserSession; import org.sonar.server.ws.WsTester; @@ -45,11 +41,10 @@ public class UninstallPluginsWsActionTest { private static final String CONTROLLER_KEY = "api/plugins"; private static final String ACTION_KEY = "uninstall"; private static final String KEY_PARAM = "key"; - private static final String PLUGIN_KEY = "pluginKey"; + private static final String PLUGIN_KEY = "findbugs"; - private PluginRepository pluginRepository = mock(PluginRepository.class); - private ServerPluginJarsInstaller pluginJarsInstaller = mock(ServerPluginJarsInstaller.class); - private UninstallPluginsWsAction underTest = new UninstallPluginsWsAction(pluginRepository, pluginJarsInstaller); + private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class); + private UninstallPluginsWsAction underTest = new UninstallPluginsWsAction(pluginRepository); private WsTester wsTester = new WsTester(new PluginsWs(underTest)); private Request invalidRequest = wsTester.newGetRequest(CONTROLLER_KEY, ACTION_KEY); @@ -109,20 +104,18 @@ public class UninstallPluginsWsActionTest { @Test public void IAE_is_raised_when_plugin_is_not_installed() throws Exception { expectedException.expect(IllegalArgumentException.class); - expectedException.expectMessage("No plugin with key 'pluginKey'"); + expectedException.expectMessage("Plugin [findbugs] is not installed"); underTest.handle(validRequest, response); } @Test public void if_plugin_is_installed_uninstallation_is_triggered() throws Exception { - when(pluginRepository.getMetadata()).thenReturn(ImmutableList.of( - DefaultPluginMetadata.create(PLUGIN_KEY) - )); + when(pluginRepository.hasPlugin(PLUGIN_KEY)).thenReturn(true); underTest.handle(validRequest, response); - verify(pluginJarsInstaller).uninstall(PLUGIN_KEY); + verify(pluginRepository).uninstall(PLUGIN_KEY); assertThat(response.outputAsString()).isEmpty(); } 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 cc96395bc62..8af9cb1eb7c 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 @@ -25,9 +25,8 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import org.sonar.server.platform.DefaultServerFileSystem; import java.io.File; @@ -45,8 +44,8 @@ public class GeneratePluginIndexTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); - private DefaultServerFileSystem fileSystem; - private File index; + DefaultServerFileSystem fileSystem; + File index; @Before public void createIndexFile() { @@ -58,9 +57,9 @@ public class GeneratePluginIndexTest { @Test public void shouldWriteIndex() throws IOException { PluginRepository repository = mock(PluginRepository.class); - PluginMetadata sqale = newMetadata("sqale"); - PluginMetadata checkstyle = newMetadata("checkstyle"); - when(repository.getMetadata()).thenReturn(Arrays.asList(sqale, checkstyle)); + PluginInfo sqale = newInfo("sqale"); + PluginInfo checkstyle = newInfo("checkstyle"); + when(repository.getPluginInfos()).thenReturn(Arrays.asList(sqale, checkstyle)); new GeneratePluginIndex(fileSystem, repository).start(); @@ -70,11 +69,7 @@ public class GeneratePluginIndexTest { assertThat(lines.get(1), containsString("checkstyle")); } - private PluginMetadata newMetadata(String pluginKey) throws IOException { - PluginMetadata plugin = mock(DefaultPluginMetadata.class); - when(plugin.getKey()).thenReturn(pluginKey); - File pluginFile = temp.newFile(pluginKey + ".jar"); - when(plugin.getFile()).thenReturn(pluginFile); - return plugin; + private PluginInfo newInfo(String pluginKey) throws IOException { + return new PluginInfo(pluginKey).setFile(temp.newFile(pluginKey + ".jar")); } } diff --git a/server/sonar-server/src/test/projects/.gitignore b/server/sonar-server/src/test/projects/.gitignore new file mode 100644 index 00000000000..a945b8525e6 --- /dev/null +++ b/server/sonar-server/src/test/projects/.gitignore @@ -0,0 +1,7 @@ +# see README.txt +!*/target/ +*/target/classes/ +*/target/maven-archiver/ +*/target/maven-status/ +*/target/test-*/ + diff --git a/server/sonar-server/src/test/projects/README.txt b/server/sonar-server/src/test/projects/README.txt new file mode 100644 index 00000000000..c53a66d52f2 --- /dev/null +++ b/server/sonar-server/src/test/projects/README.txt @@ -0,0 +1,3 @@ +This directory provides the fake plugins used by tests. These tests are rarely changed, so generated +artifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules +to build. diff --git a/server/sonar-server/src/test/projects/pom.xml b/server/sonar-server/src/test/projects/pom.xml new file mode 100644 index 00000000000..f244768228e --- /dev/null +++ b/server/sonar-server/src/test/projects/pom.xml @@ -0,0 +1,19 @@ + + + 4.0.0 + org.codehaus.sonar.tests + parent + 0.1-SNAPSHOT + pom + + test-base-plugin + test-base-plugin-v2 + test-core-plugin + test-extend-plugin + test-libs-plugin + test-require-plugin + test-requirenew-plugin + + + diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml b/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml new file mode 100644 index 00000000000..21f7ed5558d --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-base-plugin + 0.2-SNAPSHOT + sonar-plugin + Base Plugin + Simple standalone plugin. Used by other fake plugins. + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testbase + BasePlugin + + + + + + diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java b/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java new file mode 100644 index 00000000000..57b4a5dfb98 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar new file mode 100644 index 00000000000..9bd9e0e0fc1 Binary files /dev/null and b/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-base-plugin/pom.xml b/server/sonar-server/src/test/projects/test-base-plugin/pom.xml new file mode 100644 index 00000000000..61b994c4754 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-base-plugin + 0.1-SNAPSHOT + sonar-plugin + Base Plugin + Simple standalone plugin. Used by other fake plugins. + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testbase + BasePlugin + + + + + + diff --git a/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java b/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java new file mode 100644 index 00000000000..57b4a5dfb98 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class BasePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..2a6148ce7b6 Binary files /dev/null and b/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-core-plugin/pom.xml b/server/sonar-server/src/test/projects/test-core-plugin/pom.xml new file mode 100644 index 00000000000..fc3f082f6ec --- /dev/null +++ b/server/sonar-server/src/test/projects/test-core-plugin/pom.xml @@ -0,0 +1,36 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-core-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Core Plugin + Fake core plugin used by tests + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + core + CorePlugin + + + + + + diff --git a/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java b/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java new file mode 100644 index 00000000000..910204d87ea --- /dev/null +++ b/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class CorePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..62eba2aa80f Binary files /dev/null and b/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/pom.xml b/server/sonar-server/src/test/projects/test-extend-plugin/pom.xml new file mode 100644 index 00000000000..9b20de15a6a --- /dev/null +++ b/server/sonar-server/src/test/projects/test-extend-plugin/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-extend-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Extend Plugin + Fake plugin that extends the plugin with key "base" + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testextend + ExtendPlugin + testbase + + + + + + diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java b/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java new file mode 100644 index 00000000000..826e1842bbb --- /dev/null +++ b/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class ExtendPlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..7a81fdf0cce Binary files /dev/null and b/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/pom.xml b/server/sonar-server/src/test/projects/test-libs-plugin/pom.xml new file mode 100644 index 00000000000..e7d242135db --- /dev/null +++ b/server/sonar-server/src/test/projects/test-libs-plugin/pom.xml @@ -0,0 +1,49 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-libs-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Libs Plugin + Fake plugin that embeds some libraries + + + + + commons-email + commons-email + 20030310.165926 + + + commons-daemon + commons-daemon + 1.0.15 + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testlibs + LibsPlugin + + + + + + diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java b/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java new file mode 100644 index 00000000000..965c9ac7496 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class LibsPlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..85e4772f474 Binary files /dev/null and b/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-require-plugin/pom.xml b/server/sonar-server/src/test/projects/test-require-plugin/pom.xml new file mode 100644 index 00000000000..1f77e233f62 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-require-plugin/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-require-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Require Plugin + This fake plugin depends on test-base-plugin + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testrequire + RequirePlugin + testbase:0.1 + + + + + + diff --git a/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java b/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..440f73bfb58 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..ac1f9f68e46 Binary files /dev/null and b/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml b/server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml new file mode 100644 index 00000000000..ca207b10c19 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml @@ -0,0 +1,37 @@ + + + 4.0.0 + org.codehaus.sonar.tests + test-requirenew-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Require New Plugin + This fake plugin requires a version of test-base-plugin that is not installed + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testrequire + RequirePlugin + testbase:0.2 + + + + + + diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java b/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..440f73bfb58 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 00000000000..3437dfee71c Binary files /dev/null and b/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar deleted file mode 100644 index e788522ba71..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar deleted file mode 100644 index 7bcf027151a..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar deleted file mode 100644 index 0eb5f40bb8c..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar deleted file mode 100644 index 3b3ed4b1b78..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar deleted file mode 100644 index e788522ba71..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar deleted file mode 100644 index 636176b3092..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar deleted file mode 100644 index b60ea353a21..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar deleted file mode 100644 index 2e0488cebdf..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar deleted file mode 100644 index f35e77146cc..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar deleted file mode 100644 index abb19c057b5..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar deleted file mode 100644 index 7bcf027151a..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar deleted file mode 100644 index acf4fa40269..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar deleted file mode 100644 index 3b3ed4b1b78..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar deleted file mode 100644 index b781205e0d0..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar deleted file mode 100644 index 11b72f4f8eb..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar deleted file mode 100644 index 8044dff8988..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar and /dev/null differ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar deleted file mode 100644 index 19533234582..00000000000 Binary files a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar and /dev/null differ diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb index 555eb77744d..74833832017 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb @@ -48,7 +48,7 @@ class Api::UpdatecenterController < Api::ApiController hash={} hash['key']=plugin.getKey() hash['name']=plugin.getName() - hash['version']=plugin.getVersion() || '-' + hash['version']=plugin.getVersion().getName() hash end @@ -58,13 +58,13 @@ class Api::UpdatecenterController < Api::ApiController xml.plugin do xml.key(plugin.getKey()) xml.name(plugin.getName()) - xml.version(plugin.getVersion() || '-') + xml.version(plugin.getVersion().getName()) end end end end def user_plugins - java_facade.getPluginsMetadata().select{|plugin| !plugin.isCore()}.to_a.sort + java_facade.getPluginInfos().select{|plugin| !plugin.isCore()}.to_a.sort end end diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java index 0f1eea698ea..1575f757006 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java @@ -32,7 +32,7 @@ import org.sonar.api.batch.postjob.PostJob; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.utils.AnnotationUtils; import org.sonar.api.utils.dag.DirectAcyclicGraph; 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 new file mode 100644 index 00000000000..6e2c5886c60 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -0,0 +1,123 @@ +/* + * 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.annotations.VisibleForTesting; +import com.google.common.collect.Lists; +import org.apache.commons.lang.CharUtils; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.Plugin; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.plugins.RemotePlugin; +import org.sonar.core.plugins.RemotePluginFile; +import org.sonar.home.cache.FileCache; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Downloads the plugins installed on server and stores them in a local user cache + * (see {@link FileCacheProvider}). + */ +public class BatchPluginInstaller implements PluginInstaller { + + private static final Logger LOG = Loggers.get(BatchPluginInstaller.class); + + private final ServerClient server; + private final FileCache fileCache; + private final BatchPluginPredicate pluginPredicate; + + public BatchPluginInstaller(ServerClient server, FileCache fileCache, BatchPluginPredicate pluginPredicate) { + this.server = server; + this.fileCache = fileCache; + this.pluginPredicate = pluginPredicate; + } + + @Override + public Map installRemotes() { + Map infosByKey = new HashMap<>(); + for (RemotePlugin remotePlugin : listRemotePlugins()) { + if (pluginPredicate.apply(remotePlugin.getKey())) { + File jarFile = download(remotePlugin); + PluginInfo info = PluginInfo.create(jarFile); + infosByKey.put(info.getKey(), info); + } + } + return infosByKey; + } + + /** + * Returns empty on purpose. This method is used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester + */ + @Override + public Map installLocals() { + return Collections.emptyMap(); + } + + @VisibleForTesting + File download(final RemotePlugin remote) { + try { + final RemotePluginFile file = remote.file(); + return fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() { + @Override + public void download(String filename, File toFile) throws IOException { + String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename(); + if (LOG.isDebugEnabled()) { + LOG.debug("Download {} to {}", url, toFile.getAbsolutePath()); + } else { + LOG.info("Download {}", file.getFilename()); + } + server.download(url, toFile); + } + }); + + } catch (Exception e) { + throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e); + } + } + + /** + * Gets information about the plugins installed on server (filename, checksum) + */ + @VisibleForTesting + List listRemotePlugins() { + String url = "/deploy/plugins/index.txt"; + try { + LOG.debug("Download index of plugins"); + String indexContent = server.request(url); + String[] rows = StringUtils.split(indexContent, CharUtils.LF); + List result = Lists.newArrayList(); + for (String row : rows) { + result.add(RemotePlugin.unmarshal(row)); + } + return result; + + } catch (Exception e) { + throw new IllegalStateException("Fail to download list of plugins: " + url, e); + } + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java deleted file mode 100644 index 8866cf7fc0f..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.bootstrap; - -import org.sonar.api.BatchComponent; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.PluginJarInstaller; -import org.sonar.home.cache.FileCache; - -import java.io.File; -import java.io.IOException; - -public class BatchPluginJarInstaller extends PluginJarInstaller implements BatchComponent { - - private FileCache cache; - - public BatchPluginJarInstaller(FileCache cache) { - this.cache = cache; - } - - public DefaultPluginMetadata installToCache(File pluginFile, boolean isCore) { - DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore); - install(metadata, null, pluginFile); - return metadata; - } - - @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return cache.unzip(pluginFile); - } - -} 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 new file mode 100644 index 00000000000..f283dcd7247 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java @@ -0,0 +1,121 @@ +/* + * 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.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import javax.annotation.Nonnull; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Sets.newHashSet; + +/** + * Filters the plugins to be enabled during analysis + */ +public class BatchPluginPredicate implements Predicate, BatchComponent { + + private static final Logger LOG = Loggers.get(BatchPluginPredicate.class); + + 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 final Set whites = newHashSet(), blacks = newHashSet(); + private final DefaultAnalysisMode mode; + + public BatchPluginPredicate(Settings settings, DefaultAnalysisMode mode) { + this.mode = mode; + if (settings.hasKey(CoreProperties.BATCH_INCLUDE_PLUGINS)) { + whites.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS))); + } + if (settings.hasKey(CoreProperties.BATCH_EXCLUDE_PLUGINS)) { + blacks.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS))); + } + if (mode.isPreview()) { + // These default values are not supported by Settings because the class CorePlugin + // is not loaded yet. + if (settings.hasKey(CoreProperties.DRY_RUN_INCLUDE_PLUGINS)) { + LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS)); + whites.addAll(propertyValues(settings, + CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); + } else { + whites.addAll(propertyValues(settings, + CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); + } + if (settings.hasKey(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS)) { + LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS)); + blacks.addAll(propertyValues(settings, + CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); + } else { + blacks.addAll(propertyValues(settings, + CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); + } + } + if (!whites.isEmpty()) { + LOG.info("Include plugins: " + Joiner.on(", ").join(whites)); + } + if (!blacks.isEmpty()) { + LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks)); + } + } + + @Override + public boolean apply(@Nonnull String pluginKey) { + if (CORE_PLUGIN_KEY.equals(pluginKey)) { + return !mode.isMediumTest(); + } + + if (BUILDBREAKER_PLUGIN_KEY.equals(pluginKey) && mode.isPreview()) { + LOG.info("Build Breaker plugin is no more supported in preview/incremental mode"); + return false; + } + + // FIXME what happens if there are only white-listed plugins ? + List mergeList = newArrayList(blacks); + mergeList.removeAll(whites); + return mergeList.isEmpty() || !mergeList.contains(pluginKey); + } + + Set getWhites() { + return whites; + } + + Set getBlacks() { + return blacks; + } + + static List propertyValues(Settings settings, String key, String defaultValue) { + String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue); + return Lists.newArrayList(Splitter.on(",").trimResults().split(s)); + } +} 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 b8a44c0d97c..b20c85114ed 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,174 +19,69 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; +import org.picocontainer.Startable; import org.sonar.api.Plugin; -import org.sonar.api.SonarPlugin; -import org.sonar.api.config.Settings; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.PluginClassloaders; -import org.sonar.core.plugins.RemotePlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; -import java.io.File; -import java.text.MessageFormat; -import java.util.*; +import java.util.Collection; +import java.util.Map; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newHashSet; +public class BatchPluginRepository implements PluginRepository, Startable { -public class BatchPluginRepository implements PluginRepository { + private final PluginInstaller installer; + private final PluginLoader loader; - private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class); - private static final String CORE_PLUGIN = "core"; + private Map pluginInstancesByKeys; + private Map infosByKeys; - private PluginsRepository pluginsReferential; - private Map pluginsByKey; - private Map metadataByKey; - private Settings settings; - private PluginClassloaders classLoaders; - private final DefaultAnalysisMode analysisMode; - private final BatchPluginJarInstaller pluginInstaller; - - public BatchPluginRepository(PluginsRepository pluginsReferential, Settings settings, DefaultAnalysisMode analysisMode, - BatchPluginJarInstaller pluginInstaller) { - this.pluginsReferential = pluginsReferential; - this.settings = settings; - this.analysisMode = analysisMode; - this.pluginInstaller = pluginInstaller; + public BatchPluginRepository(PluginInstaller installer, PluginLoader loader) { + this.installer = installer; + this.loader = loader; } + @Override public void start() { - LOG.info("Install plugins"); - doStart(pluginsReferential.pluginList()); - - Map localPlugins = pluginsReferential.localPlugins(); - if (!localPlugins.isEmpty()) { - LOG.info("Install local plugins"); - for (Map.Entry pluginByMetadata : localPlugins.entrySet()) { - metadataByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getKey()); - pluginsByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getValue()); - } - } - } + infosByKeys = installer.installRemotes(); + pluginInstancesByKeys = loader.load(infosByKeys); - void doStart(List remotePlugins) { - PluginFilter filter = new PluginFilter(settings, analysisMode); - metadataByKey = Maps.newHashMap(); - for (RemotePlugin remote : remotePlugins) { - if (filter.accepts(remote.getKey())) { - File pluginFile = pluginsReferential.pluginFile(remote); - PluginMetadata metadata = pluginInstaller.installToCache(pluginFile, remote.isCore()); - if (StringUtils.isBlank(metadata.getBasePlugin()) || filter.accepts(metadata.getBasePlugin())) { - metadataByKey.put(metadata.getKey(), metadata); - } else { - LOG.debug("Excluded plugin: " + metadata.getKey()); - } - } + // this part is only used by tests + for (Map.Entry entry : installer.installLocals().entrySet()) { + String pluginKey = entry.getKey(); + infosByKeys.put(pluginKey, new PluginInfo(pluginKey)); + pluginInstancesByKeys.put(pluginKey, entry.getValue()); } - classLoaders = new PluginClassloaders(Thread.currentThread().getContextClassLoader()); - pluginsByKey = classLoaders.init(metadataByKey.values()); } + @Override public void stop() { - if (classLoaders != null) { - classLoaders.clean(); - classLoaders = null; - } - } + // close plugin classloaders + loader.unload(pluginInstancesByKeys.values()); - @Override - public Plugin getPlugin(String key) { - return pluginsByKey.get(key); + pluginInstancesByKeys.clear(); + infosByKeys.clear(); } @Override - public Collection getMetadata() { - return metadataByKey.values(); + public Collection getPluginInfos() { + return infosByKeys.values(); } @Override - public PluginMetadata getMetadata(String pluginKey) { - return metadataByKey.get(pluginKey); + public PluginInfo getPluginInfo(String key) { + // TODO check null result + return infosByKeys.get(key); } - public Map getPluginsByMetadata() { - Map result = Maps.newHashMap(); - for (Map.Entry entry : metadataByKey.entrySet()) { - String pluginKey = entry.getKey(); - PluginMetadata metadata = entry.getValue(); - result.put(metadata, pluginsByKey.get(pluginKey)); - } - return result; + @Override + public Plugin getPluginInstance(String key) { + // TODO check null result + return pluginInstancesByKeys.get(key); } - static class PluginFilter { - private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker"; - private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead."; - Set whites = newHashSet(), blacks = newHashSet(); - private DefaultAnalysisMode mode; - - PluginFilter(Settings settings, DefaultAnalysisMode mode) { - this.mode = mode; - if (settings.hasKey(CoreProperties.BATCH_INCLUDE_PLUGINS)) { - whites.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS))); - } - if (settings.hasKey(CoreProperties.BATCH_EXCLUDE_PLUGINS)) { - blacks.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS))); - } - if (mode.isPreview()) { - // These default values are not supported by Settings because the class CorePlugin - // is not loaded yet. - if (settings.hasKey(CoreProperties.DRY_RUN_INCLUDE_PLUGINS)) { - LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS)); - whites.addAll(propertyValues(settings, - CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); - } else { - whites.addAll(propertyValues(settings, - CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); - } - if (settings.hasKey(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS)) { - LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS)); - blacks.addAll(propertyValues(settings, - CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); - } else { - blacks.addAll(propertyValues(settings, - CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); - } - } - if (!whites.isEmpty()) { - LOG.info("Include plugins: " + Joiner.on(", ").join(whites)); - } - if (!blacks.isEmpty()) { - LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks)); - } - } - - static List propertyValues(Settings settings, String key, String defaultValue) { - String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue); - return Lists.newArrayList(Splitter.on(",").trimResults().split(s)); - } - - boolean accepts(String pluginKey) { - if (CORE_PLUGIN.equals(pluginKey)) { - return !mode.isMediumTest(); - } - - if (BUILDBREAKER_PLUGIN_KEY.equals(pluginKey) && mode.isPreview()) { - LOG.info("Build Breaker plugin is no more supported in preview/incremental mode"); - return false; - } - - List mergeList = newArrayList(blacks); - mergeList.removeAll(whites); - return mergeList.isEmpty() || !mergeList.contains(pluginKey); - } + @Override + public boolean hasPlugin(String key) { + return infosByKeys.containsKey(key); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java new file mode 100644 index 00000000000..29f554ddc89 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java @@ -0,0 +1,77 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.apache.commons.io.FileUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.utils.ZipUtils; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginUnzipper; +import org.sonar.core.platform.UnzippedPlugin; +import org.sonar.home.cache.FileCache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BatchPluginUnzipper extends PluginUnzipper implements BatchComponent { + + private final FileCache fileCache; + + public BatchPluginUnzipper(FileCache fileCache) { + this.fileCache = fileCache; + } + + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + File dir = unzipFile(info.getFile()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), dir); + } catch (Exception e) { + throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getFile().getAbsolutePath()), e); + } + } + + private File unzipFile(File cachedFile) throws IOException { + String filename = cachedFile.getName(); + File destDir = new File(cachedFile.getParentFile(), filename + "_unzip"); + File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock"); + if (!destDir.exists()) { + FileOutputStream out = new FileOutputStream(lockFile); + try { + java.nio.channels.FileLock lock = out.getChannel().lock(); + try { + // Recheck in case of concurrent processes + if (!destDir.exists()) { + File tempDir = fileCache.createTempDir(); + ZipUtils.unzip(cachedFile, tempDir, newLibFilter()); + FileUtils.moveDirectory(tempDir, destDir); + } + } finally { + lock.release(); + } + } finally { + out.close(); + FileUtils.deleteQuietly(lockFile); + } + } + return destDir; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java deleted file mode 100644 index 2687ebc56df..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.bootstrap; - -import com.google.common.collect.Lists; -import org.apache.commons.lang.CharUtils; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.RemotePlugin; -import org.sonar.core.plugins.RemotePluginFile; -import org.sonar.home.cache.FileCache; - -import java.io.File; -import java.io.IOException; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -/** - * A {@link PluginsRepository} implementation that put downloaded plugins in a FS cache. - */ -public class DefaultPluginsRepository implements PluginsRepository { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsRepository.class); - - private ServerClient server; - private FileCache fileCache; - - public DefaultPluginsRepository(FileCache fileCache, ServerClient server) { - this.server = server; - this.fileCache = fileCache; - } - - @Override - public File pluginFile(final RemotePlugin remote) { - try { - final RemotePluginFile file = remote.file(); - return fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() { - @Override - public void download(String filename, File toFile) throws IOException { - String url = "/deploy/plugins/" + remote.getKey() + "/" + file.getFilename(); - if (LOG.isDebugEnabled()) { - LOG.debug("Download {} to {}", url, toFile.getAbsolutePath()); - } else { - LOG.info("Download {}", file.getFilename()); - } - server.download(url, toFile); - } - }); - - } catch (Exception e) { - throw new IllegalStateException("Fail to download plugin: " + remote.getKey(), e); - } - } - - @Override - public List pluginList() { - String url = "/deploy/plugins/index.txt"; - try { - LOG.debug("Download index of plugins"); - String indexContent = server.request(url); - String[] rows = StringUtils.split(indexContent, CharUtils.LF); - List remoteLocations = Lists.newArrayList(); - for (String row : rows) { - remoteLocations.add(RemotePlugin.unmarshal(row)); - } - return remoteLocations; - - } catch (Exception e) { - throw new IllegalStateException("Fail to download plugins index: " + url, e); - } - } - - @Override - public Map localPlugins() { - return Collections.emptyMap(); - } - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java index 86599f96774..165aa83e649 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java @@ -21,22 +21,22 @@ package org.sonar.batch.bootstrap; import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import javax.annotation.Nullable; import java.util.List; -import java.util.Map; public class ExtensionInstaller { - private final BatchPluginRepository pluginRepository; + private final PluginRepository pluginRepository; private final EnvironmentInformation env; private final DefaultAnalysisMode analysisMode; - public ExtensionInstaller(BatchPluginRepository pluginRepository, EnvironmentInformation env, DefaultAnalysisMode analysisMode) { + public ExtensionInstaller(PluginRepository pluginRepository, EnvironmentInformation env, DefaultAnalysisMode analysisMode) { this.pluginRepository = pluginRepository; this.env = env; this.analysisMode = analysisMode; @@ -50,11 +50,10 @@ public class ExtensionInstaller { } // plugin extensions - for (Map.Entry entry : pluginRepository.getPluginsByMetadata().entrySet()) { - PluginMetadata metadata = entry.getKey(); - Plugin plugin = entry.getValue(); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + Plugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey()); for (Object extension : plugin.getExtensions()) { - doInstall(container, matcher, metadata, extension); + doInstall(container, matcher, pluginInfo, extension); } } List providers = container.getComponentsByType(ExtensionProvider.class); @@ -71,13 +70,13 @@ public class ExtensionInstaller { return this; } - private void doInstall(ComponentContainer container, ExtensionMatcher matcher, @Nullable PluginMetadata metadata, Object extension) { + private void doInstall(ComponentContainer container, ExtensionMatcher matcher, @Nullable PluginInfo pluginInfo, Object extension) { if (ExtensionUtils.supportsEnvironment(extension, env) && (analysisMode.isDb() || !ExtensionUtils.requiresDB(extension)) && matcher.accept(extension)) { - container.addExtension(metadata, extension); + container.addExtension(pluginInfo, extension); } else { - container.declareExtension(metadata, extension); + container.declareExtension(pluginInfo, extension); } } 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 7aaf0f6be32..09d217122af 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 @@ -21,28 +21,44 @@ package org.sonar.batch.bootstrap; import org.sonar.api.Plugin; import org.sonar.api.config.EmailSettings; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.utils.Durations; -import org.sonar.core.util.DefaultHttpDownloader; import org.sonar.api.utils.System2; import org.sonar.api.utils.UriReader; import org.sonar.api.utils.internal.TempFolderCleaner; import org.sonar.batch.components.PastSnapshotFinder; -import org.sonar.batch.deprecated.components.*; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByDate; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByDays; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByPreviousAnalysis; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByPreviousVersion; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByVersion; import org.sonar.batch.issue.tracking.DefaultServerLineHashesLoader; import org.sonar.batch.issue.tracking.ServerLineHashesLoader; import org.sonar.batch.platform.DefaultServer; -import org.sonar.batch.repository.*; +import org.sonar.batch.repository.DefaultGlobalRepositoriesLoader; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.batch.repository.GlobalRepositoriesProvider; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.ServerIssuesLoader; import org.sonar.batch.repository.user.UserRepository; import org.sonar.core.cluster.NullQueue; import org.sonar.core.config.Logback; import org.sonar.core.i18n.DefaultI18n; import org.sonar.core.i18n.RuleI18nManager; -import org.sonar.core.persistence.*; +import org.sonar.core.persistence.DaoUtils; +import org.sonar.core.persistence.DatabaseVersion; +import org.sonar.core.persistence.MyBatis; +import org.sonar.core.persistence.SemaphoreUpdater; +import org.sonar.core.persistence.SemaphoresImpl; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; import org.sonar.core.purge.PurgeProfiler; import org.sonar.core.rule.CacheRuleFinder; import org.sonar.core.user.HibernateUserFinder; +import org.sonar.core.util.DefaultHttpDownloader; import org.sonar.jpa.dao.MeasuresDao; import org.sonar.jpa.session.DefaultDatabaseConnector; import org.sonar.jpa.session.JpaDatabaseSession; @@ -79,11 +95,15 @@ public class GlobalContainer extends ComponentContainer { private void addBootstrapComponents() { add( + // plugins BatchPluginRepository.class, - BatchPluginJarInstaller.class, + PluginLoader.class, + BatchPluginUnzipper.class, + BatchPluginPredicate.class, + ExtensionInstaller.class, + GlobalSettings.class, ServerClient.class, - ExtensionInstaller.class, Logback.class, DefaultServer.class, new TempFolderProvider(), @@ -95,20 +115,16 @@ public class GlobalContainer extends ComponentContainer { DefaultI18n.class, new GlobalRepositoriesProvider(), UserRepository.class); - if (getComponentByType(PluginsRepository.class) == null) { - add(DefaultPluginsRepository.class); - } - if (getComponentByType(GlobalRepositoriesLoader.class) == null) { - add(DefaultGlobalRepositoriesLoader.class); - } - if (getComponentByType(ProjectRepositoriesLoader.class) == null) { - add(DefaultProjectRepositoriesLoader.class); - } - if (getComponentByType(ServerIssuesLoader.class) == null) { - add(DefaultServerIssuesLoader.class); - } - if (getComponentByType(ServerLineHashesLoader.class) == null) { - add(DefaultServerLineHashesLoader.class); + addIfMissing(BatchPluginInstaller.class, PluginInstaller.class); + addIfMissing(DefaultGlobalRepositoriesLoader.class, GlobalRepositoriesLoader.class); + addIfMissing(DefaultProjectRepositoriesLoader.class, ProjectRepositoriesLoader.class); + addIfMissing(DefaultServerIssuesLoader.class, ServerIssuesLoader.class); + addIfMissing(DefaultServerLineHashesLoader.class, ServerLineHashesLoader.class); + } + + public void addIfMissing(Object object, Class objectType) { + if (getComponentByType(objectType) == null) { + add(object); } } @@ -147,10 +163,10 @@ public class GlobalContainer extends ComponentContainer { } private void installPlugins() { - for (Map.Entry entry : getComponentByType(BatchPluginRepository.class).getPluginsByMetadata().entrySet()) { - PluginMetadata metadata = entry.getKey(); - Plugin plugin = entry.getValue(); - addExtension(metadata, plugin); + PluginRepository pluginRepository = getComponentByType(PluginRepository.class); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + Plugin instance = pluginRepository.getPluginInstance(pluginInfo.getKey()); + addExtension(pluginInfo, instance); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java new file mode 100644 index 00000000000..97eb513d4a6 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java @@ -0,0 +1,42 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.core.platform.PluginInfo; + +import java.util.Map; + +public interface PluginInstaller extends BatchComponent { + + /** + * Gets the list of plugins installed on server and downloads them if not + * already in local cache. + * @return information about all installed plugins, grouped by key + */ + Map installRemotes(); + + /** + * Used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester + */ + Map installLocals(); +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java deleted file mode 100644 index 58473fd6af1..00000000000 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.bootstrap; - -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.RemotePlugin; - -import java.io.File; -import java.util.List; -import java.util.Map; - -/** - * Plugin referential. - * @since 4.4 - */ -public interface PluginsRepository { - - /** - * Return list of remote plugins to be installed - */ - List pluginList(); - - /** - * Return location of a given plugin on the local FS. - */ - File pluginFile(RemotePlugin remote); - - /** - * Return the list of local plugins to be installed - */ - Map localPlugins(); - -} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java index b3fd7e46392..fce09a6cd1c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java @@ -23,7 +23,7 @@ import org.sonar.batch.components.PastMeasuresLoader; import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.task.Task; import org.sonar.api.task.TaskComponent; diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index e274375076b..09871955a38 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -28,27 +28,31 @@ import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.debt.internal.DefaultDebtModel; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.batch.bootstrap.PluginsRepository; import org.sonar.batch.bootstrap.TaskProperties; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.batch.issue.tracking.ServerLineHashesLoader; -import org.sonar.batch.protocol.input.*; +import org.sonar.batch.protocol.input.ActiveRule; import org.sonar.batch.protocol.input.BatchInput.ServerIssue; +import org.sonar.batch.protocol.input.FileData; +import org.sonar.batch.protocol.input.GlobalRepositories; +import org.sonar.batch.protocol.input.ProjectRepositories; import org.sonar.batch.report.ReportPublisher; import org.sonar.batch.repository.GlobalRepositoriesLoader; import org.sonar.batch.repository.ProjectRepositoriesLoader; import org.sonar.batch.repository.ServerIssuesLoader; import org.sonar.core.component.ComponentKeys; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.RemotePlugin; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; /** * Main utility class for writing batch medium tests. @@ -70,22 +74,22 @@ public class BatchMediumTester { public static class BatchMediumTesterBuilder { private final FakeGlobalRepositoriesLoader globalRefProvider = new FakeGlobalRepositoriesLoader(); private final FakeProjectRepositoriesLoader projectRefProvider = new FakeProjectRepositoriesLoader(); - private final FakePluginsRepository pluginsReferential = new FakePluginsRepository(); + private final FakePluginInstaller pluginInstaller = new FakePluginInstaller(); private final FakeServerIssuesLoader serverIssues = new FakeServerIssuesLoader(); private final FakeServerLineHashesLoader serverLineHashes = new FakeServerLineHashesLoader(); - private final Map bootstrapProperties = new HashMap(); + private final Map bootstrapProperties = new HashMap<>(); public BatchMediumTester build() { return new BatchMediumTester(this); } public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) { - pluginsReferential.addPlugin(pluginKey, location); + pluginInstaller.add(pluginKey, location); return this; } public BatchMediumTesterBuilder registerPlugin(String pluginKey, SonarPlugin instance) { - pluginsReferential.addPlugin(pluginKey, instance); + pluginInstaller.add(pluginKey, instance); return this; } @@ -164,7 +168,7 @@ public class BatchMediumTester { .setEnableLoggingConfiguration(true) .addComponents( new EnvironmentInformation("mediumTest", "1.0"), - builder.pluginsReferential, + builder.pluginInstaller, builder.globalRefProvider, builder.projectRefProvider, builder.serverIssues, @@ -280,41 +284,6 @@ public class BatchMediumTester { } - private static class FakePluginsRepository implements PluginsRepository { - - private List pluginList = new ArrayList(); - private Map pluginFiles = new HashMap(); - Map localPlugins = new HashMap(); - - @Override - public List pluginList() { - return pluginList; - } - - @Override - public File pluginFile(RemotePlugin remote) { - return pluginFiles.get(remote); - } - - public FakePluginsRepository addPlugin(String pluginKey, File location) { - RemotePlugin plugin = new RemotePlugin(pluginKey, false); - pluginList.add(plugin); - pluginFiles.put(plugin, location); - return this; - } - - public FakePluginsRepository addPlugin(String pluginKey, SonarPlugin pluginInstance) { - localPlugins.put(DefaultPluginMetadata.create(pluginKey), pluginInstance); - return this; - } - - @Override - public Map localPlugins() { - return localPlugins; - } - - } - private static class FakeServerIssuesLoader implements ServerIssuesLoader { private List serverIssues = new ArrayList<>(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java new file mode 100644 index 00000000000..cbd837c66c9 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java @@ -0,0 +1,54 @@ +/* + * 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.mediumtest; + +import org.sonar.api.Plugin; +import org.sonar.batch.bootstrap.PluginInstaller; +import org.sonar.core.platform.PluginInfo; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; + +public class FakePluginInstaller implements PluginInstaller { + + private final Map infosByKeys = new HashMap<>(); + private final Map instancesByKeys = new HashMap<>(); + + public FakePluginInstaller add(String pluginKey, File jarFile) { + infosByKeys.put(pluginKey, PluginInfo.create(jarFile)); + return this; + } + + public FakePluginInstaller add(String pluginKey, Plugin instance) { + instancesByKeys.put(pluginKey, instance); + return this; + } + + @Override + public Map installRemotes() { + return infosByKeys; + } + + @Override + public Map installLocals() { + return instancesByKeys; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 75281a9e961..f8312429543 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -27,7 +27,7 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.batch.rule.CheckFactory; import org.sonar.api.checks.NoSonarFilter; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.scan.filesystem.FileExclusions; import org.sonar.batch.ProjectTree; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index ebf3d5c2d87..12a23bf62e4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -26,7 +26,7 @@ import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.bootstrap.ProjectBootstrapper; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Languages; import org.sonar.api.resources.Project; import org.sonar.api.scan.filesystem.PathResolver; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java index 0064d028ea8..bb3f0f8a021 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java @@ -20,7 +20,7 @@ package org.sonar.batch.scan; import org.sonar.api.CoreProperties; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; import org.sonar.batch.DefaultProjectTree; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java index 14fde3895ef..6f7e0c11e32 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.sonar.api.BatchExtension; import org.sonar.api.batch.*; import org.sonar.api.batch.postjob.PostJobContext; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.batch.postjob.PostJobOptimizer; import org.sonar.batch.sensor.DefaultSensorContext; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java new file mode 100644 index 00000000000..03c3707919b --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -0,0 +1,84 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.plugins.RemotePlugin; +import org.sonar.home.cache.FileCache; + +import java.io.File; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.eq; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchPluginInstallerTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + FileCache fileCache = mock(FileCache.class); + BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class); + + @Test + public void listRemotePlugins() { + + ServerClient server = mock(ServerClient.class); + when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,false\nsqale,false"); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + + List remotePlugins = installer.listRemotePlugins(); + assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); + } + + @Test + public void should_download_plugin() throws Exception { + File pluginJar = temp.newFile(); + when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); + + ServerClient server = mock(ServerClient.class); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + + RemotePlugin remote = new RemotePlugin("checkstyle", true).setFile("checkstyle-plugin.jar", "fakemd5_1"); + File file = installer.download(remote); + + assertThat(file).isEqualTo(pluginJar); + } + + @Test + public void should_fail_to_get_plugin_index() { + thrown.expect(IllegalStateException.class); + + ServerClient server = mock(ServerClient.class); + doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); + + new BatchPluginInstaller(server, fileCache, pluginPredicate).installRemotes(); + } +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java deleted file mode 100644 index 2209c57431a..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.bootstrap; - -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.home.cache.FileCacheBuilder; - -import java.io.File; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class BatchPluginJarInstallerTest { - - private BatchPluginJarInstaller extractor; - - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File userHome; - - @Before - public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new BatchPluginJarInstaller(new FileCacheBuilder().setUserHome(userHome).build()); - } - - @Test - public void should_copy_and_extract_dependencies() throws IOException { - File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - DefaultPluginMetadata metadata = extractor.installToCache(fileFromCache, true); - - assertThat(metadata.getKey()).isEqualTo("checkstyle"); - assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); - assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists(); - } - - @Test - public void should_extract_only_dependencies() throws IOException { - File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - extractor.installToCache(fileFromCache, true); - - assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); - assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist(); - assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist(); - } - - File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginJarInstallerTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/" + 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 new file mode 100644 index 00000000000..9dcebd6d07f --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -0,0 +1,157 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.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.home.cache.FileCache; +import org.sonar.home.cache.FileCacheBuilder; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchPluginPredicateTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class); + FileCache cache; + File userHome; + + @Before + public void before() throws IOException { + userHome = temp.newFolder(); + cache = new FileCacheBuilder().setUserHome(userHome).build(); + } + + @Test + public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() { + BatchPluginPredicate predicate = new BatchPluginPredicate(new Settings(), mode); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("buildbreaker")).isTrue(); + } + + @Test + public void shouldBlackListBuildBreakerInPreviewMode() { + when(mode.isPreview()).thenReturn(true); + BatchPluginPredicate predicate = new BatchPluginPredicate(new Settings(), mode); + assertThat(predicate.apply("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"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + + @Test + public void corePluginShouldAlwaysBeInWhiteList() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("core")).isTrue(); + } + + @Test + public void corePluginShouldNeverBeInBlackList() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("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"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("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"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isTrue(); + } + + @Test + public void check_black_list_if_no_white_list() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isFalse(); + assertThat(predicate.apply("pmd")).isFalse(); + assertThat(predicate.apply("cobertura")).isTrue(); + } + + @Test + public void should_concatenate_preview_predicates() { + 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); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.getWhites()).containsOnly("cockpit"); + assertThat(predicate.getBlacks()).containsOnly("views", "checkstyle", "pmd"); + } + + @Test + public void should_concatenate_deprecated_dry_run_predicates() { + 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); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.getWhites()).containsOnly("cockpit"); + assertThat(predicate.getBlacks()).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"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + +} 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 66a499b01c0..7c82edbb64f 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,219 +17,237 @@ * 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.assertj.core.api.Assertions.assertThat; -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); - - DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); - when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - - repository = new BatchPluginRepository(downloader, 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(); - } - -} +///* +// * 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(); +// } +// +//} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java new file mode 100644 index 00000000000..06a25148775 --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.batch.bootstrap; + +import org.apache.commons.io.FileUtils; +import org.junit.Before; +import org.junit.ClassRule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.UnzippedPlugin; +import org.sonar.home.cache.FileCache; +import org.sonar.home.cache.FileCacheBuilder; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; + +public class BatchPluginUnzipperTest { + + @ClassRule + public static TemporaryFolder temp = new TemporaryFolder(); + + File userHome; + BatchPluginUnzipper underTest; + + @Before + public void setUp() throws IOException { + userHome = temp.newFolder(); + FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build(); + underTest = new BatchPluginUnzipper(fileCache); + } + + @Test + public void copy_and_extract_libs() throws IOException { + File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); + UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache)); + + assertThat(unzipped.getKey()).isEqualTo("checkstyle"); + assertThat(unzipped.getMain()).isFile().exists(); + assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar"); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists(); + } + + @Test + public void extract_only_libs() throws IOException { + File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); + underTest.unzip(PluginInfo.create(fileFromCache)); + + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist(); + assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/org/sonar/plugins/checkstyle/CheckstyleVersion.class")).doesNotExist(); + } + + File getFileFromCache(String filename) throws IOException { + File src = FileUtils.toFile(BatchPluginUnzipperTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename)); + File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); + FileUtils.copyFile(src, destFile); + return destFile; + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java deleted file mode 100644 index 57e940ab7ae..00000000000 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.batch.bootstrap; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.junit.rules.TemporaryFolder; -import org.sonar.core.plugins.RemotePlugin; -import org.sonar.home.cache.FileCache; - -import java.io.File; -import java.util.List; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.eq; -import static org.mockito.Mockito.doThrow; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class DefaultPluginsRepositoryTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void should_request_list_of_plugins() { - FileCache cache = mock(FileCache.class); - ServerClient server = mock(ServerClient.class); - when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false"); - DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server); - - List plugins = downloader.pluginList(); - assertThat(plugins).hasSize(2); - assertThat(plugins.get(0).getKey()).isEqualTo("checkstyle"); - assertThat(plugins.get(0).isCore()).isTrue(); - assertThat(plugins.get(1).getKey()).isEqualTo("sqale"); - assertThat(plugins.get(1).isCore()).isFalse(); - } - - @Test - public void should_download_plugin() throws Exception { - FileCache cache = mock(FileCache.class); - - File pluginJar = temp.newFile(); - when(cache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); - - ServerClient server = mock(ServerClient.class); - DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server); - - RemotePlugin plugin = new RemotePlugin("checkstyle", true) - .setFile("checkstyle-plugin.jar", "fakemd5_1"); - File file = downloader.pluginFile(plugin); - - assertThat(file).isEqualTo(pluginJar); - } - - @Test - public void should_fail_to_get_plugin_index() { - thrown.expect(IllegalStateException.class); - - ServerClient server = mock(ServerClient.class); - doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); - - new DefaultPluginsRepository(mock(FileCache.class), server).pluginList(); - } -} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java index 0647b0e22ec..b01040b29cd 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java @@ -19,7 +19,6 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.collect.Maps; import org.apache.commons.lang.ClassUtils; import org.junit.Before; import org.junit.Test; @@ -28,13 +27,12 @@ import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; import org.sonar.api.SonarPlugin; import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; import java.util.Arrays; import java.util.List; -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -42,19 +40,15 @@ import static org.mockito.Mockito.when; public class ExtensionInstallerTest { - private DefaultAnalysisMode mode; - PluginMetadata metadata = mock(PluginMetadata.class); + DefaultAnalysisMode mode; + BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - Map newPlugin(final Object... extensions) { - Map result = Maps.newHashMap(); - result.put(metadata, - new SonarPlugin() { - public List getExtensions() { - return Arrays.asList(extensions); - } + private static Plugin newPluginInstance(final Object... extensions) { + return new SonarPlugin() { + public List getExtensions() { + return Arrays.asList(extensions); } - ); - return result; + }; } @Before @@ -64,8 +58,9 @@ public class ExtensionInstallerTest { @Test public void should_filter_extensions_to_install() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(Foo.class, Bar.class)); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, Bar.class)); + ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); installer.install(container, new FooMatcher()); @@ -76,8 +71,8 @@ public class ExtensionInstallerTest { @Test public void should_execute_extension_provider() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(new FooProvider(), new BarProvider())); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooProvider(), new BarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); @@ -89,8 +84,8 @@ public class ExtensionInstallerTest { @Test public void should_provide_list_of_extensions() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(new FooBarProvider())); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooBarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); @@ -102,9 +97,8 @@ public class ExtensionInstallerTest { @Test public void should_not_install_on_unsupported_environment() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(Foo.class, MavenExtension.class, AntExtension.class, new BarProvider())); - + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, MavenExtension.class, AntExtension.class, new BarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java index 9fce2c7a951..ac87c52fa51 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java @@ -19,25 +19,15 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import org.junit.Test; import org.sonar.api.BatchExtension; -import org.sonar.api.Plugin; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.utils.TempFolder; import org.sonar.core.config.Logback; -import java.util.Arrays; import java.util.Collections; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public class GlobalContainerTest { @Test @@ -58,22 +48,6 @@ public class GlobalContainerTest { assertThat(container.getComponentByType(Bar.class)).isNotNull(); } - @Test - public void should_install_plugins() { - PluginMetadata metadata = mock(PluginMetadata.class); - FakePlugin plugin = new FakePlugin(); - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(ImmutableMap.of( - metadata, plugin - )); - - GlobalContainer container = spy(GlobalContainer.create(Collections.emptyMap(), Lists.newArrayList(pluginRepository))); - doNothing().when(container).executeTask(Collections.emptyMap()); - container.doAfterStart(); - - assertThat(container.getComponentsByType(Plugin.class)).containsOnly(plugin); - } - public static class Foo implements BatchExtension { } @@ -82,10 +56,4 @@ public class GlobalContainerTest { } - public static class FakePlugin extends SonarPlugin { - - public List getExtensions() { - return Arrays.asList(Foo.class, Bar.class); - } - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java b/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java index fd9a9ed3727..3b7340689db 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java @@ -25,7 +25,7 @@ import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.DependedUpon; import org.sonar.api.measures.*; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.batch.bootstrap.BatchExtensionDictionnary; diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java index 5c98be19187..b4f7dc49ce3 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java @@ -30,7 +30,7 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.task.TaskExtension; import org.sonar.api.utils.System2; import org.sonar.api.utils.TempFolder; diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar deleted file mode 100644 index f937399bec5..00000000000 Binary files a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar and /dev/null differ diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar new file mode 100644 index 00000000000..f937399bec5 Binary files /dev/null and b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar differ diff --git a/sonar-check-api/src/main/java/org/sonar/check/package-info.java b/sonar-check-api/src/main/java/org/sonar/check/package-info.java index b1f3232a62f..bd348edb5c0 100644 --- a/sonar-check-api/src/main/java/org/sonar/check/package-info.java +++ b/sonar-check-api/src/main/java/org/sonar/check/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.check; diff --git a/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java b/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java index 551a67c296f..d0abbe987a4 100644 --- a/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java +++ b/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.colorizer; diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml index 41ae4b71bff..ea0fd0be8f8 100644 --- a/sonar-core/pom.xml +++ b/sonar-core/pom.xml @@ -19,15 +19,13 @@ jsr305 provided + + org.codehaus.sonar + sonar-classloader + ${project.groupId} sonar-plugin-api - - - classworlds - classworlds - - org.mybatis @@ -89,10 +87,6 @@ commons-dbutils commons-dbutils - - org.codehaus.plexus - plexus-classworlds - com.googlecode.json-simple json-simple diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java index 02281c51f9e..8190b0f6e3e 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java @@ -20,41 +20,49 @@ package org.sonar.core.i18n; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; +import com.google.common.base.Preconditions; import org.apache.commons.io.IOUtils; import org.picocontainer.Startable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.BatchExtension; -import org.sonar.api.ServerExtension; import org.sonar.api.i18n.I18n; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.SonarException; import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.NumberFormat; -import java.util.*; - -public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Startable { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultI18n.class); - - public static final String BUNDLE_PACKAGE = "org.sonar.l10n."; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +public class DefaultI18n implements I18n, Startable { + + private static final Logger LOG = Loggers.get(DefaultI18n.class); + private static final String BUNDLE_PACKAGE = "org.sonar.l10n."; + + private final PluginRepository pluginRepository; + private final ResourceBundle.Control control; + private final System2 system2; - private PluginRepository pluginRepository; + // the following fields are available after startup private ClassLoader classloader; private Map propertyToBundles; - private final ResourceBundle.Control control; - private final System2 system2; public DefaultI18n(PluginRepository pluginRepository) { this(pluginRepository, System2.INSTANCE); @@ -68,9 +76,7 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start this.control = new ResourceBundle.Control() { @Override public Locale getFallbackLocale(String baseName, Locale locale) { - if (baseName == null) { - throw new NullPointerException(); - } + Preconditions.checkNotNull(baseName); Locale defaultLocale = Locale.ENGLISH; return locale.equals(defaultLocale) ? null : defaultLocale; } @@ -85,16 +91,16 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @VisibleForTesting void doStart(ClassLoader classloader) { this.classloader = classloader; - propertyToBundles = Maps.newHashMap(); - Collection metadata = pluginRepository.getMetadata(); - if (metadata.isEmpty()) { + this.propertyToBundles = new HashMap<>(); + Collection infos = pluginRepository.getPluginInfos(); + if (infos.isEmpty()) { addPlugin("core"); } else { - for (PluginMetadata plugin : pluginRepository.getMetadata()) { + for (PluginInfo plugin : infos) { addPlugin(plugin.getKey()); } } - LOG.debug(String.format("Loaded %d properties from l10n bundles", propertyToBundles.size())); + LOG.debug("Loaded {} properties from l10n bundles", propertyToBundles.size()); } private void addPlugin(String pluginKey) { @@ -113,6 +119,9 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @Override public void stop() { + if (classloader instanceof Closeable) { + IOUtils.closeQuietly((Closeable)classloader); + } classloader = null; propertyToBundles = null; } diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java index d0eb0510763..9647eb784ca 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java @@ -19,35 +19,30 @@ */ package org.sonar.core.i18n; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; import java.util.List; +/** + * Aggregation of all plugin and core classloaders, used to search for all l10n bundles + */ class I18nClassloader extends URLClassLoader { - private static List classLoadersFromPlugin(PluginRepository pluginRepository) { - List list = Lists.newArrayList(); - for (PluginMetadata metadata : pluginRepository.getMetadata()) { - Plugin plugin = pluginRepository.getPlugin(metadata.getKey()); - list.add(plugin.getClass().getClassLoader()); - } - return list; - } - - private ClassLoader[] pluginClassloaders; + private final ClassLoader[] pluginClassloaders; public I18nClassloader(PluginRepository pluginRepository) { - this(classLoadersFromPlugin(pluginRepository)); + this(allPluginClassloaders(pluginRepository)); } + @VisibleForTesting I18nClassloader(List pluginClassloaders) { super(new URL[0]); - pluginClassloaders.add(getClass().getClassLoader()); this.pluginClassloaders = pluginClassloaders.toArray(new ClassLoader[pluginClassloaders.size()]); } @@ -59,7 +54,7 @@ class I18nClassloader extends URLClassLoader { return url; } } - return null; + return getClass().getClassLoader().getResource(name); } @Override @@ -71,4 +66,15 @@ class I18nClassloader extends URLClassLoader { public String toString() { return "i18n-classloader"; } + + private static List allPluginClassloaders(PluginRepository pluginRepository) { + // accepted limitation: some plugins extend base plugins, sharing the same classloader, so + // there may be duplicated classloaders in the list. + List list = Lists.newArrayList(); + for (PluginInfo info : pluginRepository.getPluginInfos()) { + Plugin plugin = pluginRepository.getPluginInstance(info.getKey()); + list.add(plugin.getClass().getClassLoader()); + } + return list; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java index 2d51c65fac8..f4dc7f69eca 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.core.issue.workflow; diff --git a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java b/sonar-core/src/main/java/org/sonar/core/notification/package-info.java index de0f088a8f3..c53b6d4b186 100644 --- a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/notification/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.core.notification; diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java new file mode 100644 index 00000000000..868208b12a0 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java @@ -0,0 +1,250 @@ +/* + * 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.collect.Iterables; +import org.picocontainer.Characteristics; +import org.picocontainer.ComponentAdapter; +import org.picocontainer.DefaultPicoContainer; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.behaviors.OptInCaching; +import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; +import org.picocontainer.monitors.NullComponentMonitor; +import org.sonar.api.BatchComponent; +import org.sonar.api.ServerComponent; +import org.sonar.api.config.PropertyDefinitions; + +import javax.annotation.Nullable; + +import java.util.Collection; +import java.util.List; + +public class ComponentContainer implements BatchComponent, ServerComponent { + + // no need for multiple children + ComponentContainer parent, child; + MutablePicoContainer pico; + PropertyDefinitions propertyDefinitions; + ComponentKeys componentKeys; + + /** + * Create root container + */ + public ComponentContainer() { + this.parent = null; + this.child = null; + this.pico = createPicoContainer(); + this.componentKeys = new ComponentKeys(); + propertyDefinitions = new PropertyDefinitions(); + addSingleton(propertyDefinitions); + addSingleton(this); + } + + /** + * Create child container + */ + protected ComponentContainer(ComponentContainer parent) { + this.parent = parent; + this.pico = parent.pico.makeChildContainer(); + this.parent.child = this; + this.propertyDefinitions = parent.propertyDefinitions; + this.componentKeys = new ComponentKeys(); + addSingleton(this); + } + + public void execute() { + boolean threw = true; + try { + startComponents(); + threw = false; + } finally { + stopComponents(threw); + } + } + + /** + * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting + * a component twice is not authorized. + */ + public ComponentContainer startComponents() { + try { + doBeforeStart(); + pico.start(); + doAfterStart(); + return this; + } catch (Exception e) { + throw PicoUtils.propagate(e); + } + } + + /** + * This method aims to be overridden + */ + protected void doBeforeStart() { + // nothing + } + + /** + * This method aims to be overridden + */ + protected void doAfterStart() { + // nothing + } + + /** + * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting + * a component twice is not authorized. + */ + public ComponentContainer stopComponents() { + return stopComponents(false); + } + + public ComponentContainer stopComponents(boolean swallowException) { + try { + pico.stop(); + pico.dispose(); + + } catch (RuntimeException e) { + if (!swallowException) { + throw PicoUtils.propagate(e); + } + } finally { + removeChild(); + if (parent != null) { + parent.removeChild(); + } + } + return this; + } + + /** + * @since 3.5 + */ + public ComponentContainer add(Object... objects) { + for (Object object : objects) { + if (object instanceof ComponentAdapter) { + addPicoAdapter((ComponentAdapter) object); + } else if (object instanceof Iterable) { + add(Iterables.toArray((Iterable) object, Object.class)); + } else { + addSingleton(object); + } + } + return this; + } + + public ComponentContainer addSingletons(Collection components) { + for (Object component : components) { + addSingleton(component); + } + return this; + } + + public ComponentContainer addSingleton(Object component) { + return addComponent(component, true); + } + + /** + * @param singleton return always the same instance if true, else a new instance + * is returned each time the component is requested + */ + public ComponentContainer addComponent(Object component, boolean singleton) { + Object key = componentKeys.of(component); + if (component instanceof ComponentAdapter) { + pico.addAdapter((ComponentAdapter) component); + } else { + try { + pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component); + } catch (Throwable t) { + throw new IllegalStateException("Unable to register component " + getName(component), t); + } + declareExtension(null, component); + } + return this; + } + + public ComponentContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) { + Object key = componentKeys.of(extension); + try { + pico.as(Characteristics.CACHE).addComponent(key, extension); + } catch (Throwable t) { + throw new IllegalStateException("Unable to register extension " + getName(extension), t); + } + declareExtension(pluginInfo, extension); + return this; + } + + private String getName(Object extension) { + if (extension instanceof Class) { + return ((Class) extension).getName(); + } + return getName(extension.getClass()); + } + + public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) { + propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : ""); + } + + public ComponentContainer addPicoAdapter(ComponentAdapter adapter) { + pico.addAdapter(adapter); + return this; + } + + public T getComponentByType(Class tClass) { + return pico.getComponent(tClass); + } + + public Object getComponentByKey(Object key) { + return pico.getComponent(key); + } + + public List getComponentsByType(Class tClass) { + return pico.getComponents(tClass); + } + + public ComponentContainer removeChild() { + if (child != null) { + pico.removeChildContainer(child.pico); + child = null; + } + return this; + } + + public ComponentContainer createChild() { + return new ComponentContainer(this); + } + + public static MutablePicoContainer createPicoContainer() { + ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close"); + return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); + } + + public ComponentContainer getParent() { + return parent; + } + + public ComponentContainer getChild() { + return child; + } + + public MutablePicoContainer getPicoContainer() { + return pico; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java new file mode 100644 index 00000000000..be315cd9cf7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java @@ -0,0 +1,52 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.sonar.api.utils.internal.Uuids; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +class ComponentKeys { + + private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(".+@[a-f0-9]+"); + private final Set objectsWithoutToString = new HashSet<>(); + + Object of(Object component) { + return of(component, Loggers.get(ComponentKeys.class)); + } + + Object of(Object component, Logger log) { + if (component instanceof Class) { + return component; + } + String key = component.toString(); + if (IDENTITY_HASH_PATTERN.matcher(key).matches()) { + if (!objectsWithoutToString.add(component.getClass())) { + log.warn(String.format("Bad component key: %s. Please implement toString() method on class %s", key, component.getClass().getName())); + } + key += Uuids.create(); + } + return new StringBuilder().append(component.getClass().getCanonicalName()).append("-").append(key).toString(); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java new file mode 100644 index 00000000000..ee4e1ae42a5 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java @@ -0,0 +1,47 @@ +/* + * 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.Throwables; +import org.picocontainer.PicoLifecycleException; + +class PicoUtils { + + private PicoUtils() { + // only static methods + } + + static Throwable sanitize(Throwable t) { + Throwable result = t; + Throwable cause = t.getCause(); + if (t instanceof PicoLifecycleException && cause != null) { + if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) { + result = cause.getCause(); + } else { + result = cause; + } + } + return result; + } + + static RuntimeException propagate(Throwable t) { + throw Throwables.propagate(sanitize(t)); + } +} 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 new file mode 100644 index 00000000000..e5509bbc7e7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java @@ -0,0 +1,353 @@ +/* + * 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.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import org.apache.commons.lang.StringUtils; +import org.sonar.updatecenter.common.PluginManifest; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class PluginInfo implements Comparable { + + public static class RequiredPlugin { + private final String key; + private final Version minimalVersion; + + public RequiredPlugin(String key, Version minimalVersion) { + this.key = key; + this.minimalVersion = minimalVersion; + } + + public String getKey() { + return key; + } + + public Version getMinimalVersion() { + return minimalVersion; + } + + public static RequiredPlugin parse(String s) { + if (!s.matches("\\w+:.+")) { + 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()); + } + } + + private File file; + private String key; + private String name; + private Version version; + private Version minimalSqVersion; + private String mainClass; + private String description; + private String organizationName; + private String organizationUrl; + private String license; + private String homepageUrl; + private String issueTrackerUrl; + private boolean useChildFirstClassLoader; + private String basePlugin; + private boolean core; + private String implementationBuild; + private final List requiredPlugins = new ArrayList<>(); + + public PluginInfo() { + } + + /** + * For tests only + */ + public PluginInfo(String key) { + this.key = key; + } + + public File getFile() { + return file; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + @CheckForNull + public Version getVersion() { + return version; + } + + @CheckForNull + public Version getMinimalSqVersion() { + return minimalSqVersion; + } + + public String getMainClass() { + return mainClass; + } + + @CheckForNull + public String getDescription() { + return description; + } + + @CheckForNull + public String getOrganizationName() { + return organizationName; + } + + @CheckForNull + public String getOrganizationUrl() { + return organizationUrl; + } + + @CheckForNull + public String getLicense() { + return license; + } + + @CheckForNull + public String getHomepageUrl() { + return homepageUrl; + } + + @CheckForNull + public String getIssueTrackerUrl() { + return issueTrackerUrl; + } + + public boolean isUseChildFirstClassLoader() { + return useChildFirstClassLoader; + } + + @CheckForNull + public String getBasePlugin() { + return basePlugin; + } + + public boolean isCore() { + return core; + } + + @CheckForNull + public String getImplementationBuild() { + return implementationBuild; + } + + public List getRequiredPlugins() { + return requiredPlugins; + } + + /** + * Required + */ + public PluginInfo setFile(File file) { + this.file = file; + 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; + } + + public PluginInfo setMinimalSqVersion(@Nullable Version v) { + this.minimalSqVersion = v; + return this; + } + + /** + * Required + */ + public PluginInfo setMainClass(String mainClass) { + this.mainClass = mainClass; + return this; + } + + public PluginInfo setDescription(@Nullable String description) { + this.description = description; + return this; + } + + public PluginInfo setOrganizationName(@Nullable String s) { + this.organizationName = s; + return this; + } + + public PluginInfo setOrganizationUrl(@Nullable String s) { + this.organizationUrl = s; + return this; + } + + public PluginInfo setLicense(@Nullable String license) { + this.license = license; + return this; + } + + public PluginInfo setHomepageUrl(@Nullable String s) { + this.homepageUrl = s; + return this; + } + + public PluginInfo setIssueTrackerUrl(@Nullable String s) { + this.issueTrackerUrl = s; + return this; + } + + public PluginInfo setUseChildFirstClassLoader(boolean b) { + this.useChildFirstClassLoader = b; + return this; + } + + public PluginInfo setBasePlugin(@Nullable String s) { + this.basePlugin = s; + return this; + } + + public PluginInfo setCore(boolean b) { + this.core = b; + return this; + } + + public PluginInfo setImplementationBuild(@Nullable String implementationBuild) { + this.implementationBuild = implementationBuild; + return this; + } + + public PluginInfo addRequiredPlugin(RequiredPlugin p) { + this.requiredPlugins.add(p); + return this; + } + + /** + * Find out if this plugin is compatible with a given version of SonarQube. + * The version of SQ must be greater than or equal to the minimal version + * needed by the plugin. + */ + public boolean isCompatibleWith(String sqVersion) { + if (null == this.minimalSqVersion) { + // no constraint defined on the plugin + return true; + } + + Version effectiveMin = Version.create(minimalSqVersion.getName()).removeQualifier(); + Version actualVersion = Version.create(sqVersion).removeQualifier(); + return actualVersion.compareTo(effectiveMin) >= 0; + } + + @Override + public String toString() { + return String.format("[%s]", Joiner.on(" / ").skipNulls().join(key, version, implementationBuild)); + } + + @Override + public int compareTo(PluginInfo other) { + int cmp = name.compareTo(other.name); + if (cmp != 0) { + return cmp; + } + return version.compareTo(other.version); + } + + public static PluginInfo create(File jarFile) { + try { + PluginManifest manifest = new PluginManifest(jarFile); + return create(jarFile, manifest); + + } catch (Exception e) { + throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e); + } + } + + @VisibleForTesting + static PluginInfo create(File jarFile, PluginManifest manifest) { + PluginInfo info = new PluginInfo(); + + // required fields + info.setKey(manifest.getKey()); + info.setFile(jarFile); + info.setName(manifest.getName()); + info.setMainClass(manifest.getMainClass()); + info.setVersion(Version.create(manifest.getVersion())); + + // optional fields + info.setDescription(manifest.getDescription()); + info.setLicense(manifest.getLicense()); + info.setOrganizationName(manifest.getOrganization()); + info.setOrganizationUrl(manifest.getOrganizationUrl()); + String minSqVersion = manifest.getSonarVersion(); + if (minSqVersion != null) { + info.setMinimalSqVersion(Version.create(minSqVersion)); + } + info.setHomepageUrl(manifest.getHomepage()); + info.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); + info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); + info.setBasePlugin(manifest.getBasePlugin()); + info.setImplementationBuild(manifest.getImplementationBuild()); + String[] requiredPlugins = manifest.getRequirePlugins(); + if (requiredPlugins != null) { + for (String s : requiredPlugins) { + info.addRequiredPlugin(RequiredPlugin.parse(s)); + } + } + return info; + } + + public enum JarToPluginInfo implements Function { + INSTANCE; + + @Override + public PluginInfo apply(@Nonnull File jarFile) { + return create(jarFile); + } + }; +} 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 new file mode 100644 index 00000000000..336c4904bcf --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java @@ -0,0 +1,199 @@ +/* + * 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.Strings; +import org.apache.commons.lang.SystemUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +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 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, ...). + *

+ * 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. + *

+ * This class is stateless. It does not keep classloaders and {@link Plugin} in memory. + */ +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 PluginUnzipper unzipper; + + public PluginLoader(PluginUnzipper unzipper) { + this.unzipper = unzipper; + } + + public Map load(Map infoByKeys) { + Collection defs = defineClassloaders(infoByKeys).values(); + buildClassloaders(defs); + return instantiatePluginInstances(defs); + } + + /** + * Step 1 - define the different classloaders to be created. Number of classloaders can be + * different than number of plugins. + */ + Map defineClassloaders(Map infoByKeys) { + Map classloadersByBasePlugin = new HashMap<>(); + + for (PluginInfo info : infoByKeys.values()) { + String baseKey = basePluginKey(info, infoByKeys); + ClassloaderDef def = classloadersByBasePlugin.get(baseKey); + if (def == null) { + 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()); + for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { + def.mask.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(); + } + } + return classloadersByBasePlugin; + } + + /** + * Step 2 - create classloaders with appropriate constituents and metadata + */ + 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)); + } + } + Map classloadersByBasePluginKey = builder.build(); + for (ClassloaderDef def : defs) { + def.classloader = classloadersByBasePluginKey.get(def.basePluginKey); + } + } + + /** + * Step 3 - instantiate plugin instances ({@link Plugin} + * + * @return the instances grouped by plugin key + * @throws IllegalStateException if at least one plugin can't be correctly loaded + */ + 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()) { + String pluginKey = entry.getKey(); + String mainClass = entry.getValue(); + try { + instancesByPluginKey.put(pluginKey, (Plugin) def.classloader.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); + } catch (Exception e) { + throw new IllegalStateException(String.format( + "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e); + } + } + } + return instancesByPluginKey; + } + + public void unload(Collection plugins) { + for (Plugin plugin : plugins) { + ClassLoader classLoader = plugin.getClass().getClassLoader(); + if (classLoader instanceof Closeable && classLoader != getClass().getClassLoader()) { + try { + ((Closeable) classLoader).close(); + } catch (Exception e) { + Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e); + } + } + } + } + + /** + * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then + * B and C must be attached to the classloader of A. The method returns A in the three cases. + */ + static String basePluginKey(PluginInfo plugin, Map allPluginsPerKey) { + String base = plugin.getKey(); + String parentKey = plugin.getBasePlugin(); + while (!Strings.isNullOrEmpty(parentKey)) { + PluginInfo parentPlugin = allPluginsPerKey.get(parentKey); + base = parentPlugin.getKey(); + parentKey = parentPlugin.getBasePlugin(); + } + return base; + } + + private URL fileToUrl(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java new file mode 100644 index 00000000000..1b3b170a938 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java @@ -0,0 +1,43 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.api.ServerComponent; + +import java.util.Collection; + +/** + * Provides information about the plugins installed in the dependency injection container + */ +public interface PluginRepository extends BatchComponent, ServerComponent { + + Collection getPluginInfos(); + + PluginInfo getPluginInfo(String key); + + /** + * @return the instance of {@link Plugin} for the given plugin key. Never return null. + */ + Plugin getPluginInstance(String key); + + boolean hasPlugin(String key); +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java new file mode 100644 index 00000000000..5ce1ca08da7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java @@ -0,0 +1,40 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.sonar.api.utils.ZipUtils; + +import java.util.zip.ZipEntry; + +public abstract class PluginUnzipper { + + protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib"; + + public abstract UnzippedPlugin unzip(PluginInfo info); + + protected ZipUtils.ZipEntryFilter newLibFilter() { + return new ZipUtils.ZipEntryFilter() { + @Override + public boolean accept(ZipEntry entry) { + return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR); + } + }; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java new file mode 100644 index 00000000000..3c73b8d658b --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java @@ -0,0 +1,62 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; + +import static org.apache.commons.io.FileUtils.listFiles; + +public class UnzippedPlugin { + + private final String key; + private final File main; + private final Collection libs; + + public UnzippedPlugin(String key, File main, Collection libs) { + this.key = key; + this.main = main; + this.libs = libs; + } + + public String getKey() { + return key; + } + + public File getMain() { + return main; + } + + 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/package-info.java b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java new file mode 100644 index 00000000000..d93af63a5ba --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * Provides support of DI (Dependency Injection) container and management of plugins. + */ +@ParametersAreNonnullByDefault +package org.sonar.core.platform; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java b/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java deleted file mode 100644 index a65036b7d94..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import com.google.common.collect.ImmutableList; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.updatecenter.common.Version; - -import java.io.File; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; - -public class DefaultPluginMetadata implements PluginMetadata, Comparable { - private File file; - private List deployedFiles; - private List pathsToInternalDeps; - private String key; - private String version; - private String sonarVersion; - private String name; - private String mainClass; - private String description; - private String organization; - private String organizationUrl; - private String license; - private String homepage; - private String issueTrackerUrl; - private boolean useChildFirstClassLoader; - private String basePlugin; - private boolean core; - private String implementationBuild; - private List requiredPlugins; - - private DefaultPluginMetadata() { - deployedFiles = newArrayList(); - pathsToInternalDeps = newArrayList(); - requiredPlugins = newArrayList(); - } - - public static DefaultPluginMetadata create(File file) { - return new DefaultPluginMetadata().setFile(file); - } - - public static DefaultPluginMetadata create(String key) { - return new DefaultPluginMetadata().setKey(key); - } - - @Override - public File getFile() { - return file; - } - - public DefaultPluginMetadata setFile(File file) { - this.file = file; - return this; - } - - @Override - public List getDeployedFiles() { - return deployedFiles; - } - - public DefaultPluginMetadata addDeployedFile(File f) { - this.deployedFiles.add(f); - return this; - } - - public List getPathsToInternalDeps() { - return ImmutableList.copyOf(pathsToInternalDeps); - } - - public DefaultPluginMetadata setPathsToInternalDeps(List pathsToInternalDeps) { - this.pathsToInternalDeps = ImmutableList.copyOf(pathsToInternalDeps); - return this; - } - - @Override - public String getKey() { - return key; - } - - public DefaultPluginMetadata setKey(String key) { - this.key = key; - return this; - } - - @Override - public String getName() { - return name; - } - - public DefaultPluginMetadata setName(String name) { - this.name = name; - return this; - } - - @Override - public String getMainClass() { - return mainClass; - } - - public DefaultPluginMetadata setMainClass(String mainClass) { - this.mainClass = mainClass; - return this; - } - - @Override - public String getDescription() { - return description; - } - - public DefaultPluginMetadata setDescription(String description) { - this.description = description; - return this; - } - - @Override - public String getOrganization() { - return organization; - } - - public DefaultPluginMetadata setOrganization(String organization) { - this.organization = organization; - return this; - } - - @Override - public String getOrganizationUrl() { - return organizationUrl; - } - - public DefaultPluginMetadata setOrganizationUrl(String organizationUrl) { - this.organizationUrl = organizationUrl; - return this; - } - - @Override - public String getLicense() { - return license; - } - - public DefaultPluginMetadata setLicense(String license) { - this.license = license; - return this; - } - - @Override - public String getVersion() { - return version; - } - - public DefaultPluginMetadata setVersion(String version) { - this.version = version; - return this; - } - - public String getSonarVersion() { - return sonarVersion; - } - - public DefaultPluginMetadata setSonarVersion(String sonarVersion) { - this.sonarVersion = sonarVersion; - return this; - } - - @Override - public List getRequiredPlugins() { - return ImmutableList.copyOf(requiredPlugins); - } - - public DefaultPluginMetadata setRequiredPlugins(List requiredPlugins) { - this.requiredPlugins = ImmutableList.copyOf(requiredPlugins); - return this; - } - - /** - * Find out if this plugin is compatible with a given version of Sonar. - * The version of sonar must be greater than or equal to the minimal version - * needed by the plugin. - * - * @param sonarVersion - * @return true if the plugin is compatible - */ - public boolean isCompatibleWith(String sonarVersion) { - if (null == this.sonarVersion) { - // Plugins without sonar version are so old, they are compatible with a version containing this code - return true; - } - - Version minimumVersion = Version.create(this.sonarVersion).removeQualifier(); - Version actualVersion = Version.create(sonarVersion).removeQualifier(); - return actualVersion.compareTo(minimumVersion) >= 0; - } - - @Override - public String getHomepage() { - return homepage; - } - - public DefaultPluginMetadata setHomepage(String homepage) { - this.homepage = homepage; - return this; - } - - @Override - public String getIssueTrackerUrl() { - return issueTrackerUrl; - } - - public DefaultPluginMetadata setIssueTrackerUrl(String issueTrackerUrl) { - this.issueTrackerUrl = issueTrackerUrl; - return this; - } - - public DefaultPluginMetadata setUseChildFirstClassLoader(boolean use) { - this.useChildFirstClassLoader = use; - return this; - } - - @Override - public boolean isUseChildFirstClassLoader() { - return useChildFirstClassLoader; - } - - public DefaultPluginMetadata setBasePlugin(String key) { - this.basePlugin = key; - return this; - } - - @Override - public String getBasePlugin() { - return basePlugin; - } - - @Override - public boolean isCore() { - return core; - } - - public DefaultPluginMetadata setCore(boolean b) { - this.core = b; - return this; - } - - @Override - public String getImplementationBuild() { - return implementationBuild; - } - - public DefaultPluginMetadata setImplementationBuild(String implementationBuild) { - this.implementationBuild = implementationBuild; - return this; - } - - @Override - public String getParent() { - return null; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DefaultPluginMetadata that = (DefaultPluginMetadata) o; - return key == null ? that.key == null : key.equals(that.key); - } - - @Override - public int hashCode() { - return key != null ? key.hashCode() : 0; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("key", key) - .append("version", StringUtils.defaultIfEmpty(version, "-")) - .toString(); - } - - @Override - public int compareTo(PluginMetadata other) { - return name.compareTo(other.getName()); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java deleted file mode 100644 index 8a6b4b88ef1..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.SystemUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.net.URL; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on - * {@link ClassWorld}. - *

- *

IMPORTANT

- *

- * If we have pluginA , then all classes and resources from package and subpackages of org.sonar.plugins.pluginA.api will be visible - * for all other plugins even if they are located in dependent library. - *

- *

- *

Search order for {@link ClassRealm} :

- *
    - *
  • parent class loader (passed via the constructor) if there is one
  • - *
  • imports
  • - *
  • realm's constituents
  • - *
  • parent realm
  • - *
- */ -public class PluginClassloaders { - - private static final String[] PREFIXES_TO_EXPORT = {"org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins."}; - private static final Logger LOG = LoggerFactory.getLogger(PluginClassloaders.class); - - private ClassWorld world; - private ClassLoader baseClassloader; - private boolean done = false; - - public PluginClassloaders(ClassLoader baseClassloader) { - this(baseClassloader, new ClassWorld()); - } - - @VisibleForTesting - PluginClassloaders(ClassLoader baseClassloader, ClassWorld world) { - this.baseClassloader = baseClassloader; - this.world = world; - } - - public Map init(Collection plugins) { - List children = Lists.newArrayList(); - for (PluginMetadata plugin : plugins) { - if (StringUtils.isBlank(plugin.getBasePlugin())) { - add(plugin); - } else { - children.add(plugin); - } - } - - for (PluginMetadata child : children) { - extend(child); - } - - done(); - - Map pluginsByKey = Maps.newHashMap(); - for (PluginMetadata metadata : plugins) { - pluginsByKey.put(metadata.getKey(), instantiatePlugin(metadata)); - } - return pluginsByKey; - } - - public ClassLoader add(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - List resources = Lists.newArrayList(); - List others = Lists.newArrayList(); - for (File file : plugin.getDeployedFiles()) { - if (isResource(file)) { - resources.add(file.toURI().toURL()); - } else { - others.add(file.toURI().toURL()); - } - } - ClassLoader parent; - if (resources.isEmpty()) { - parent = baseClassloader; - } else { - parent = new ResourcesClassloader(resources, baseClassloader); - } - ClassRealm realm; - if (plugin.isUseChildFirstClassLoader()) { - ClassRealm parentRealm = world.newRealm(plugin.getKey() + "-parent", parent); - realm = parentRealm.createChildRealm(plugin.getKey()); - } else { - realm = world.newRealm(plugin.getKey(), parent); - } - for (URL url : others) { - realm.addURL(url); - } - return realm; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", plugin.getKey(), - SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to build the classloader of %s", plugin.getKey()), e); - } - } - - public boolean extend(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - ClassRealm base = world.getRealm(plugin.getBasePlugin()); - if (base == null) { - // Ignored, because base plugin is not installed - LOG.warn(String.format("Plugin %s is ignored because base plugin is not installed: %s", - plugin.getKey(), plugin.getBasePlugin())); - return false; - } - // we create new realm to be able to return it by key without conversion to baseKey - base.createChildRealm(plugin.getKey()); - for (File file : plugin.getDeployedFiles()) { - base.addURL(file.toURI().toURL()); - } - return true; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to extend the plugin %s for %s", - plugin.getBasePlugin(), plugin.getKey()), e); - } - } - - /** - * Establishes dependencies among ClassLoaders. - */ - public void done() { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - for (Object o : world.getRealms()) { - ClassRealm realm = (ClassRealm) o; - if (!StringUtils.endsWith(realm.getId(), "-parent")) { - String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; - for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { - // important to have dot at the end of package name only for classworlds 1.1 - packagesToExport[i] = String.format("%s%s.api", PREFIXES_TO_EXPORT[i], realm.getId()); - } - export(realm, packagesToExport); - } - } - done = true; - } - - /** - * Exports specified packages from given ClassRealm to all others. - */ - private void export(ClassRealm realm, String... packages) { - for (Object o : world.getRealms()) { - ClassRealm dep = (ClassRealm) o; - if (!StringUtils.equals(dep.getId(), realm.getId())) { - try { - for (String packageName : packages) { - dep.importFrom(realm.getId(), packageName); - } - } catch (NoSuchRealmException e) { - // should never happen - throw new SonarException(e); - } - } - } - } - - /** - * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. - */ - public ClassLoader get(String key) { - if (!done) { - throw new IllegalStateException("Plugin classloaders are not initialized"); - } - try { - return world.getRealm(key); - } catch (NoSuchRealmException e) { - return null; - } - } - - public Plugin instantiatePlugin(PluginMetadata plugin) { - try { - Class clazz = get(plugin.getKey()).loadClass(plugin.getMainClass()); - return (Plugin) clazz.newInstance(); - - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to load plugin %s", plugin.getKey()), e); - } - } - - private boolean isResource(File file) { - return !StringUtils.endsWithIgnoreCase(file.getName(), ".jar") && !file.isDirectory(); - } - - public void clean() { - for (ClassRealm realm : world.getRealms()) { - try { - world.disposeRealm(realm.getId()); - } catch (Exception e) { - // Ignore - } - } - world = null; - baseClassloader=null; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java deleted file mode 100644 index 7c5114a323b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import com.google.common.base.Function; -import org.sonar.api.BatchComponent; -import org.sonar.api.ServerComponent; -import org.sonar.api.utils.SonarException; -import org.sonar.updatecenter.common.PluginManifest; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -public abstract class PluginJarInstaller implements BatchComponent, ServerComponent { - - protected static final String FAIL_TO_INSTALL_PLUGIN = "Fail to install plugin: "; - - protected void install(DefaultPluginMetadata metadata, @Nullable File pluginBasedir, File deployedPlugin) { - try { - metadata.addDeployedFile(deployedPlugin); - copyDependencies(metadata, deployedPlugin, pluginBasedir); - } catch (IOException e) { - throw new SonarException(FAIL_TO_INSTALL_PLUGIN + metadata, e); - } - } - - private void copyDependencies(DefaultPluginMetadata metadata, File pluginFile, @Nullable File pluginBasedir) throws IOException { - if (!metadata.getPathsToInternalDeps().isEmpty()) { - // needs to unzip the jar - File baseDir = extractPluginDependencies(pluginFile, pluginBasedir); - for (String depPath : metadata.getPathsToInternalDeps()) { - File dependency = new File(baseDir, depPath); - if (!dependency.isFile() || !dependency.exists()) { - throw new IllegalArgumentException("Dependency " + depPath + " can not be found in " + pluginFile.getName()); - } - metadata.addDeployedFile(dependency); - } - } - } - - protected abstract File extractPluginDependencies(File pluginFile, @Nullable File pluginBasedir) throws IOException; - - public DefaultPluginMetadata extractMetadata(File file, boolean isCore) { - try { - PluginManifest manifest = new PluginManifest(file); - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(file); - metadata.setKey(manifest.getKey()); - metadata.setName(manifest.getName()); - metadata.setDescription(manifest.getDescription()); - metadata.setLicense(manifest.getLicense()); - metadata.setOrganization(manifest.getOrganization()); - metadata.setOrganizationUrl(manifest.getOrganizationUrl()); - metadata.setMainClass(manifest.getMainClass()); - metadata.setVersion(manifest.getVersion()); - metadata.setSonarVersion(manifest.getSonarVersion()); - metadata.setHomepage(manifest.getHomepage()); - metadata.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); - metadata.setPathsToInternalDeps(Arrays.asList(manifest.getDependencies())); - metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); - metadata.setBasePlugin(manifest.getBasePlugin()); - metadata.setImplementationBuild(manifest.getImplementationBuild()); - metadata.setRequiredPlugins(Arrays.asList(manifest.getRequirePlugins())); - metadata.setCore(isCore); - return metadata; - - } catch (IOException e) { - throw new IllegalStateException("Fail to extract plugin metadata from file: " + file, e); - } - } - - public Function fileToPlugin() { - return jarFileToPlugin; - } - - public Function fileToCorePlugin() { - return jarFileToCorePlugin; - } - - private final Function jarFileToCorePlugin = new Function() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, true); - } - }; - private final Function jarFileToPlugin = new Function() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, false); - } - }; -} 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 3bc254a3ff1..6fecfe0ae91 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 @@ -20,6 +20,7 @@ package org.sonar.core.plugins; import org.apache.commons.lang.StringUtils; +import org.sonar.core.platform.PluginInfo; import org.sonar.home.cache.FileHashes; import java.io.File; @@ -34,7 +35,7 @@ public class RemotePlugin { this.core = core; } - public static RemotePlugin create(DefaultPluginMetadata metadata) { + public static RemotePlugin create(PluginInfo metadata) { RemotePlugin result = new RemotePlugin(metadata.getKey(), metadata.isCore()); result.setFile(metadata.getFile()); return result; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java b/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java deleted file mode 100644 index 04aea1fb391..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import com.google.common.collect.Lists; -import org.apache.commons.lang.StringUtils; - -import java.net.URL; -import java.net.URLClassLoader; -import java.util.Collection; - -/** - * This class loader is used to load resources from a list of URLs - see SONAR-1861. - */ -public class ResourcesClassloader extends URLClassLoader { - private Collection urls; - - public ResourcesClassloader(Collection urls, ClassLoader parent) { - super(new URL[] {}, parent); - this.urls = Lists.newArrayList(urls); - } - - @Override - public URL findResource(String name) { - for (URL url : urls) { - if (StringUtils.endsWith(url.getPath(), name)) { - return url; - } - } - return null; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java index d3208e7de69..e95a48da83b 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java @@ -17,9 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ + @ParametersAreNonnullByDefault package org.sonar.core.plugins; diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java index b3acbde594a..110694aff65 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java @@ -24,10 +24,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -51,8 +51,8 @@ public class DefaultI18nTest { @Before public void before() { PluginRepository pluginRepository = mock(PluginRepository.class); - List plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); - when(pluginRepository.getMetadata()).thenReturn(plugins); + List plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); + when(pluginRepository.getPluginInfos()).thenReturn(plugins); manager = new DefaultI18n(pluginRepository, system2); manager.doStart(getClass().getClassLoader()); @@ -222,8 +222,8 @@ public class DefaultI18nTest { return new URLClassLoader(urls); } - private PluginMetadata newPlugin(String key) { - PluginMetadata plugin = mock(PluginMetadata.class); + private PluginInfo newPlugin(String key) { + PluginInfo plugin = mock(PluginInfo.class); when(plugin.getKey()).thenReturn(key); return plugin; } diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java index 971a7573965..19f9add77cc 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -80,7 +80,7 @@ public class I18nClassloaderTest { private static URLClassLoader newClassLoader(String... resourcePaths) { URL[] urls = new URL[resourcePaths.length]; for (int index = 0; index < resourcePaths.length; index++) { - urls[index] = DefaultI18nTest.class.getResource(resourcePaths[index]); + urls[index] = I18nClassloaderTest.class.getResource(resourcePaths[index]); } return new URLClassLoader(urls); } diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java new file mode 100644 index 00000000000..5d7923d23bd --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java @@ -0,0 +1,392 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.Property; +import org.sonar.api.config.PropertyDefinitions; + +import java.util.Arrays; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +public class ComponentContainerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void shouldRegisterItself() { + ComponentContainer container = new ComponentContainer(); + assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container); + } + + @Test + public void should_start_and_stop() { + ComponentContainer container = spy(new ComponentContainer()); + container.addSingleton(StartableComponent.class); + container.startComponents(); + + assertThat(container.getComponentByType(StartableComponent.class).started).isTrue(); + assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse(); + verify(container).doBeforeStart(); + verify(container).doAfterStart(); + + container.stopComponents(); + assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue(); + } + + @Test + public void should_start_and_stop_hierarchy_of_containers() { + StartableComponent parentComponent = new StartableComponent(); + final StartableComponent childComponent = new StartableComponent(); + ComponentContainer parentContainer = new ComponentContainer() { + @Override + public void doAfterStart() { + ComponentContainer childContainer = new ComponentContainer(this); + childContainer.add(childComponent); + childContainer.execute(); + } + }; + parentContainer.add(parentComponent); + parentContainer.execute(); + assertThat(parentComponent.started).isTrue(); + assertThat(parentComponent.stopped).isTrue(); + assertThat(childComponent.started).isTrue(); + assertThat(childComponent.stopped).isTrue(); + } + + @Test + public void should_stop_hierarchy_of_containers_on_failure() { + StartableComponent parentComponent = new StartableComponent(); + final StartableComponent childComponent1 = new StartableComponent(); + final UnstartableComponent childComponent2 = new UnstartableComponent(); + ComponentContainer parentContainer = new ComponentContainer() { + @Override + public void doAfterStart() { + ComponentContainer childContainer = new ComponentContainer(this); + childContainer.add(childComponent1); + childContainer.add(childComponent2); + childContainer.execute(); + } + }; + parentContainer.add(parentComponent); + try { + parentContainer.execute(); + fail(); + } catch (Exception e) { + assertThat(parentComponent.started).isTrue(); + assertThat(parentComponent.stopped).isTrue(); + assertThat(childComponent1.started).isTrue(); + assertThat(childComponent1.stopped).isTrue(); + } + } + + @Test + public void testChild() { + ComponentContainer parent = new ComponentContainer(); + parent.startComponents(); + + ComponentContainer child = parent.createChild(); + child.addSingleton(StartableComponent.class); + child.startComponents(); + + assertThat(child.getParent()).isSameAs(parent); + assertThat(parent.getChild()).isSameAs(child); + assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child); + assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent); + assertThat(child.getComponentByType(StartableComponent.class)).isNotNull(); + assertThat(parent.getComponentByType(StartableComponent.class)).isNull(); + + parent.stopComponents(); + } + + @Test + public void testRemoveChild() { + ComponentContainer parent = new ComponentContainer(); + parent.startComponents(); + + ComponentContainer child = parent.createChild(); + assertThat(parent.getChild()).isSameAs(child); + + parent.removeChild(); + assertThat(parent.getChild()).isNull(); + } + + @Test + public void shouldForwardStartAndStopToDescendants() { + ComponentContainer grandParent = new ComponentContainer(); + ComponentContainer parent = grandParent.createChild(); + ComponentContainer child = parent.createChild(); + child.addSingleton(StartableComponent.class); + + grandParent.startComponents(); + + StartableComponent component = child.getComponentByType(StartableComponent.class); + assertTrue(component.started); + + parent.stopComponents(); + assertTrue(component.stopped); + } + + @Test + public void shouldDeclareComponentProperties() { + ComponentContainer container = new ComponentContainer(); + container.addSingleton(ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar"); + } + + @Test + public void shouldDeclareExtensionWithoutAddingIt() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + container.declareExtension(plugin, ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull(); + } + + @Test + public void shouldDeclareExtensionWhenAdding() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + container.addExtension(plugin, ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull(); + } + + @Test + public void test_add_class() { + ComponentContainer container = new ComponentContainer(); + container.add(ComponentWithProperty.class, SimpleComponent.class); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void test_add_collection() { + ComponentContainer container = new ComponentContainer(); + container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class)); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void test_add_adapter() { + ComponentContainer container = new ComponentContainer(); + container.add(new SimpleComponentProvider()); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void should_sanitize_pico_exception_on_start_failure() { + ComponentContainer container = new ComponentContainer(); + container.add(UnstartableComponent.class); + + // do not expect a PicoException + thrown.expect(IllegalStateException.class); + container.startComponents(); + } + + @Test + public void display_plugin_name_when_failing_to_add_extension() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + + container.startComponents(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent"); + + container.addExtension(plugin, UnstartableComponent.class); + + } + + @Test + public void test_start_failure() { + ComponentContainer container = new ComponentContainer(); + StartableComponent startable = new StartableComponent(); + container.add(startable, UnstartableComponent.class); + + try { + container.execute(); + fail(); + } catch (Exception e) { + assertThat(startable.started).isTrue(); + + // container stops the components that have already been started + assertThat(startable.stopped).isTrue(); + } + } + + @Test + public void test_stop_failure() { + ComponentContainer container = new ComponentContainer(); + StartableComponent startable = new StartableComponent(); + container.add(startable, UnstoppableComponent.class); + + try { + container.execute(); + fail(); + } catch (Exception e) { + assertThat(startable.started).isTrue(); + + // container should stop the components that have already been started + // ... but that's not the case + } + } + + @Test + public void stop_exception_should_not_hide_start_exception() { + ComponentContainer container = new ComponentContainer(); + container.add(UnstartableComponent.class, UnstoppableComponent.class); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Fail to start"); + container.execute(); + } + + @Test + public void should_execute_components() { + ComponentContainer container = new ComponentContainer(); + StartableComponent component = new StartableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.started).isTrue(); + assertThat(component.stopped).isTrue(); + } + + /** + * Method close() must be called even if the methods start() or stop() + * are not defined. + */ + @Test + public void should_close_components_without_lifecycle() { + ComponentContainer container = new ComponentContainer(); + CloseableComponent component = new CloseableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.isClosed).isTrue(); + } + + /** + * Method close() must be executed after stop() + */ + @Test + public void should_close_components_with_lifecycle() { + ComponentContainer container = new ComponentContainer(); + StartableCloseableComponent component = new StartableCloseableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.isStopped).isTrue(); + assertThat(component.isClosed).isTrue(); + assertThat(component.isClosedAfterStop).isTrue(); + } + + public static class StartableComponent { + public boolean started = false, stopped = false; + + public void start() { + started = true; + } + + public void stop() { + stopped = true; + } + } + + public static class UnstartableComponent { + public void start() { + throw new IllegalStateException("Fail to start"); + } + + public void stop() { + + } + } + + public static class UnstoppableComponent { + public void start() { + } + + public void stop() { + throw new IllegalStateException("Fail to stop"); + } + } + + @Property(key = "foo", defaultValue = "bar", name = "Foo") + public static class ComponentWithProperty { + + } + + public static class SimpleComponent { + + } + + public static class SimpleComponentProvider extends ProviderAdapter { + public SimpleComponent provide() { + return new SimpleComponent(); + } + } + + public static class CloseableComponent implements AutoCloseable { + public boolean isClosed = false; + + @Override + public void close() throws Exception { + isClosed = true; + } + } + + public static class StartableCloseableComponent implements AutoCloseable { + public boolean isClosed = false, isStopped = false, isClosedAfterStop = false; + + public void stop() { + isStopped = true; + } + + @Override + public void close() throws Exception { + isClosed = true; + isClosedAfterStop = isStopped; + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java new file mode 100644 index 00000000000..431890bc012 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java @@ -0,0 +1,80 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.junit.Test; +import org.sonar.api.utils.log.Logger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class ComponentKeysTest { + + ComponentKeys keys = new ComponentKeys(); + + @Test + public void generate_key_of_class() { + assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class); + } + + @Test + public void generate_key_of_object() throws Exception { + assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.core.platform.ComponentKeysTest.FakeComponent-fake"); + } + + @Test + public void should_log_warning_if_toString_is_not_overridden() { + Logger log = mock(Logger.class); + keys.of(new Object(), log); + verifyZeroInteractions(log); + + // only on non-first runs, to avoid false-positives on singletons + keys.of(new Object(), log); + verify(log).warn(startsWith("Bad component key")); + } + + @Test + public void should_generate_unique_key_when_toString_is_not_overridden() { + Object key = keys.of(new WrongToStringImpl()); + assertThat(key).isNotEqualTo(WrongToStringImpl.KEY); + + Object key2 = keys.of(new WrongToStringImpl()); + assertThat(key2).isNotEqualTo(key); + } + + static class FakeComponent { + @Override + public String toString() { + return "fake"; + } + } + + static class WrongToStringImpl { + static final String KEY = "my.Component@123a"; + + @Override + public String toString() { + return KEY; + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java new file mode 100644 index 00000000000..67b488584a9 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java @@ -0,0 +1,106 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.junit.Test; +import org.picocontainer.Characteristics; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoLifecycleException; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class PicoUtilsTest { + + @Test + public void shouldSanitizePicoLifecycleException() { + Throwable th = PicoUtils.sanitize(newPicoLifecycleException(false)); + + assertThat(th).isInstanceOf(IllegalStateException.class); + assertThat(th.getMessage()).isEqualTo("A good reason to fail"); + } + + @Test + public void shouldSanitizePicoLifecycleException_no_wrapper_message() { + Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg"))); + + assertThat(th).isInstanceOf(IllegalStateException.class); + assertThat(th.getMessage()).isEqualTo("msg"); + } + + @Test + public void shouldNotSanitizeOtherExceptions() { + Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo")); + + assertThat(th).isInstanceOf(IllegalArgumentException.class); + assertThat(th.getMessage()).isEqualTo("foo"); + } + + @Test + public void shouldPropagateInitialUncheckedException() { + try { + PicoUtils.propagate(newPicoLifecycleException(false)); + fail(); + } catch (RuntimeException e) { + assertThat(e).isInstanceOf(IllegalStateException.class); + } + } + + @Test + public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() { + try { + PicoUtils.propagate(newPicoLifecycleException(true)); + fail(); + } catch (RuntimeException e) { + assertThat(e.getCause()).isInstanceOf(IOException.class); + assertThat(e.getCause().getMessage()).isEqualTo("Checked"); + } + } + + private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) { + MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE); + if (initialCheckedException) { + container.addComponent(CheckedFailureComponent.class); + } else { + container.addComponent(UncheckedFailureComponent.class); + } + try { + container.start(); + return null; + + } catch (PicoLifecycleException e) { + return e; + } + } + + public static class UncheckedFailureComponent { + public void start() { + throw new IllegalStateException("A good reason to fail"); + } + } + + public static class CheckedFailureComponent { + public void start() throws IOException { + throw new IOException("Checked"); + } + } +} 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 new file mode 100644 index 00000000000..2b702748c4b --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java @@ -0,0 +1,203 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.ManifestUtils; +import org.sonar.updatecenter.common.PluginManifest; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.Nullable; + +import java.io.File; +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; +import static org.junit.Assert.fail; + +public class PluginInfoTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void test_RequiredPlugin() throws Exception { + PluginInfo.RequiredPlugin plugin = PluginInfo.RequiredPlugin.parse("java:1.1"); + assertThat(plugin.getKey()).isEqualTo("java"); + assertThat(plugin.getMinimalVersion().getName()).isEqualTo("1.1"); + + try { + PluginInfo.RequiredPlugin.parse("java"); + fail(); + } catch (IllegalArgumentException expected) { + // ok + } + } + + @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); + 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); + } + + @Test + public void test_compatibility_with_sq_version() { + 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(); + + assertThat(withMinSqVersion("1.0").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); + assertThat(withMinSqVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); + + assertThat(withMinSqVersion("1.1").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); + assertThat(withMinSqVersion("2.10").isCompatibleWith("2.1")).isFalse(); + assertThat(withMinSqVersion("10.10").isCompatibleWith("2.2")).isFalse(); + + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); + assertThat(withMinSqVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + + assertThat(withMinSqVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); + + assertThat(withMinSqVersion(null).isCompatibleWith("0")).isTrue(); + assertThat(withMinSqVersion(null).isCompatibleWith("3.1")).isTrue(); + } + + @Test + public void create_from_minimal_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("java"); + manifest.setVersion("1.0"); + manifest.setName("Java"); + manifest.setMainClass("org.foo.FooPlugin"); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getKey()).isEqualTo("java"); + assertThat(pluginInfo.getName()).isEqualTo("Java"); + assertThat(pluginInfo.getVersion().getName()).isEqualTo("1.0"); + assertThat(pluginInfo.getFile()).isSameAs(jarFile); + assertThat(pluginInfo.getMainClass()).isEqualTo("org.foo.FooPlugin"); + + // optional fields + assertThat(pluginInfo.getBasePlugin()).isNull(); + assertThat(pluginInfo.getDescription()).isNull(); + assertThat(pluginInfo.getHomepageUrl()).isNull(); + assertThat(pluginInfo.getImplementationBuild()).isNull(); + assertThat(pluginInfo.getIssueTrackerUrl()).isNull(); + assertThat(pluginInfo.getLicense()).isNull(); + assertThat(pluginInfo.getOrganizationName()).isNull(); + assertThat(pluginInfo.getOrganizationUrl()).isNull(); + assertThat(pluginInfo.getMinimalSqVersion()).isNull(); + assertThat(pluginInfo.getRequiredPlugins()).isEmpty(); + } + + @Test + public void create_from_complete_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("fbcontrib"); + manifest.setVersion("2.0"); + manifest.setName("Java"); + manifest.setMainClass("org.fb.FindbugsPlugin"); + manifest.setBasePlugin("findbugs"); + manifest.setSonarVersion("4.5.1"); + manifest.setDescription("the desc"); + manifest.setHomepage("http://fbcontrib.org"); + manifest.setImplementationBuild("SHA1"); + manifest.setLicense("LGPL"); + manifest.setOrganization("SonarSource"); + manifest.setOrganizationUrl("http://sonarsource.com"); + manifest.setIssueTrackerUrl("http://jira.com"); + manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"}); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getBasePlugin()).isEqualTo("findbugs"); + assertThat(pluginInfo.getDescription()).isEqualTo("the desc"); + assertThat(pluginInfo.getHomepageUrl()).isEqualTo("http://fbcontrib.org"); + assertThat(pluginInfo.getImplementationBuild()).isEqualTo("SHA1"); + assertThat(pluginInfo.getIssueTrackerUrl()).isEqualTo("http://jira.com"); + assertThat(pluginInfo.getLicense()).isEqualTo("LGPL"); + assertThat(pluginInfo.getOrganizationName()).isEqualTo("SonarSource"); + assertThat(pluginInfo.getOrganizationUrl()).isEqualTo("http://sonarsource.com"); + assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo("4.5.1"); + assertThat(pluginInfo.getRequiredPlugins()).extracting("key").containsExactly("java", "pmd"); + } + + @Test + public void create_from_file() throws Exception { + File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar")); + PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar); + + assertThat(checkstyleInfo.getName()).isEqualTo("Checkstyle"); + assertThat(checkstyleInfo.getMinimalSqVersion()).isEqualTo(Version.create("2.8")); + } + + @Test + public void test_toString() throws Exception { + PluginInfo pluginInfo = new PluginInfo().setKey("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 pluginInfo = new PluginInfo("foo"); + if (version != null) { + pluginInfo.setMinimalSqVersion(Version.create(version)); + } + return pluginInfo; + } +} 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 new file mode 100644 index 00000000000..7cb984b1bda --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java @@ -0,0 +1,131 @@ +/* + * 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.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.assertj.core.data.MapEntry; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.Plugin; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class PluginLoaderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void complete_test() 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()); + Map instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo)); + + assertThat(instances).containsOnlyKeys("checkstyle"); + Plugin checkstyleInstance = instances.get("checkstyle"); + assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin"); + + loader.unload(instances.values()); + // should test that classloaders are closed + } + + @Test + public void define_plugin_classloader__nominal() throws Exception { + PluginInfo info = new PluginInfo("foo") + .setName("Foo") + .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)); + + 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")); + // TODO test mask - require change in sonar-classloader + } + + @Test + public void define_plugin_classloader__extend_base_plugin() throws Exception { + File baseJarFile = temp.newFile(), extensionJarFile = temp.newFile(); + PluginInfo base = new PluginInfo("foo") + .setName("Foo") + .setMainClass("org.foo.FooPlugin") + .setFile(baseJarFile); + PluginInfo extension = new PluginInfo("fooContrib") + .setName("Foo Contrib") + .setMainClass("org.foo.ContribPlugin") + .setFile(extensionJarFile) + .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)); + + 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")); + // TODO test mask - require change in sonar-classloader + } + + /** + * Does not unzip jar file. + */ + private class BasicPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return new UnzippedPlugin(info.getKey(), info.getFile(), Collections.emptyList()); + } + } + + private class TempPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + File tempDir = temp.newFolder(); + ZipUtils.unzip(info.getFile(), tempDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), tempDir); + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java new file mode 100644 index 00000000000..cbdd9c23356 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java @@ -0,0 +1,81 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.core.platform; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginUnzipperTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void unzip_plugin_with_libs() throws Exception { + final File jarFile = getFile("sonar-checkstyle-plugin-2.8.jar"); + final File toDir = temp.newFolder(); + PluginInfo pluginInfo = new PluginInfo().setKey("checkstyle").setFile(jarFile); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + ZipUtils.unzip(jarFile, toDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), toDir); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + }; + UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); + assertThat(unzipped.getKey()).isEqualTo("checkstyle"); + assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar"); + assertThat(unzipped.getMain()).isSameAs(jarFile); + } + + @Test + public void unzip_plugin_without_libs() throws Exception { + File jarFile = temp.newFile(); + final File toDir = temp.newFolder(); + PluginInfo pluginInfo = new PluginInfo().setFile(jarFile); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir); + } + }; + UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); + assertThat(unzipped.getKey()).isEqualTo("foo"); + assertThat(unzipped.getLibs()).isEmpty(); + assertThat(unzipped.getMain()).isSameAs(jarFile); + } + + private File getFile(String filename) { + return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java deleted file mode 100644 index 98cdf2698b6..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Ordering.natural; -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultPluginMetadataTest { - - @Test - public void testGettersAndSetters() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")); - metadata.setKey("checkstyle") - .setLicense("LGPL") - .setDescription("description") - .setHomepage("http://home") - .setIssueTrackerUrl("http://jira.codehuas.org") - .setMainClass("org.Main") - .setOrganization("SonarSource") - .setOrganizationUrl("http://sonarsource.org") - .setVersion("1.1") - .setSonarVersion("3.0") - .setUseChildFirstClassLoader(true) - .setCore(false) - .setImplementationBuild("abcdef"); - - assertThat(metadata.getKey()).isEqualTo("checkstyle"); - assertThat(metadata.getParent()).isNull(); - assertThat(metadata.getLicense()).isEqualTo("LGPL"); - assertThat(metadata.getDescription()).isEqualTo("description"); - assertThat(metadata.getHomepage()).isEqualTo("http://home"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehuas.org"); - assertThat(metadata.getMainClass()).isEqualTo("org.Main"); - assertThat(metadata.getOrganization()).isEqualTo("SonarSource"); - assertThat(metadata.getOrganizationUrl()).isEqualTo("http://sonarsource.org"); - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("3.0"); - assertThat(metadata.isUseChildFirstClassLoader()).isTrue(); - assertThat(metadata.isCore()).isFalse(); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getFile()).isNotNull(); - assertThat(metadata.getDeployedFiles()).isEmpty(); - assertThat(metadata.getImplementationBuild()).isEqualTo("abcdef"); - } - - @Test - public void testDeployedFiles() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .addDeployedFile(new File("foo.jar")) - .addDeployedFile(new File("bar.jar")); - - assertThat(metadata.getDeployedFiles()).hasSize(2); - } - - @Test - public void testInternalPathToDependencies() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setPathsToInternalDeps(newArrayList("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar")); - - assertThat(metadata.getPathsToInternalDeps()).containsOnly("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar"); - } - - @Test - public void shouldEquals() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle"); - PluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")).setKey("pmd"); - - assertThat(checkstyle).isEqualTo(checkstyle); - assertThat(checkstyle).isEqualTo(DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle")); - assertThat(checkstyle).isNotEqualTo(pmd); - } - - @Test - public void shouldCompare() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setKey("checkstyle") - .setName("Checkstyle"); - DefaultPluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")) - .setKey("pmd") - .setName("PMD"); - List plugins = Arrays.asList(pmd, checkstyle); - - assertThat(natural().sortedCopy(plugins)).extracting("key").containsExactly("checkstyle", "pmd"); - } - - @Test - public void should_check_compatibility_with_sonar_version() { - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1.0")).isTrue(); - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.0.0")).isTrue(); - - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); - assertThat(pluginWithVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); - - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); - assertThat(pluginWithVersion("2.10").isCompatibleWith("2.1")).isFalse(); - assertThat(pluginWithVersion("10.10").isCompatibleWith("2.2")).isFalse(); - - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); - assertThat(pluginWithVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - - assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); - - assertThat(pluginWithVersion(null).isCompatibleWith("0")).isTrue(); - assertThat(pluginWithVersion(null).isCompatibleWith("3.1")).isTrue(); - } - - static DefaultPluginMetadata pluginWithVersion(String version) { - return DefaultPluginMetadata.create("foo").setSonarVersion(version); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java deleted file mode 100644 index d9b3ada4254..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import org.apache.commons.io.FileUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.util.Arrays; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PluginClassloadersTest { - - private PluginClassloaders classloaders; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void before() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - } - - @After - public void clean() { - if (classloaders != null) { - classloaders.clean(); - } - } - - @Test - public void shouldImport() { - classloaders.add(DefaultPluginMetadata.create("foo").addDeployedFile(getFile("PluginClassloadersTest/foo.jar"))); - classloaders.add(DefaultPluginMetadata.create("bar").addDeployedFile(getFile("PluginClassloadersTest/bar.jar"))); - classloaders.done(); - - String resourceName = "org/sonar/plugins/bar/api/resource.txt"; - assertThat(classloaders.get("bar").getResourceAsStream(resourceName)).isNotNull(); - assertThat(classloaders.get("foo").getResourceAsStream(resourceName)).isNotNull(); - } - - @Test - public void shouldCreateBaseClassloader() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - Map map = classloaders.init(Arrays.asList(checkstyle)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - ClassRealm checkstyleRealm = (ClassRealm) checkstyleEntryPoint.getClass().getClassLoader(); - assertThat(checkstyleRealm.getId()).isEqualTo("checkstyle"); - } - - @Test - public void shouldExtendPlugin() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - DefaultPluginMetadata checkstyleExt = DefaultPluginMetadata.create("checkstyle-ext") - .setBasePlugin("checkstyle") - .setMainClass("com.mycompany.sonar.checkstyle.CheckstyleExtensionsPlugin") - .addDeployedFile(getFile("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); - - Map map = classloaders.init(Arrays.asList(checkstyle, checkstyleExt)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - Plugin checkstyleExtEntryPoint = map.get("checkstyle-ext"); - - assertThat(checkstyleEntryPoint.getClass().getClassLoader().equals(checkstyleExtEntryPoint.getClass().getClassLoader())).isTrue(); - } - - @Test - public void detect_plugins_compiled_for_bad_java_version() throws Exception { - thrown.expect(SonarException.class); - thrown.expectMessage("The plugin checkstyle is not supported with Java 1."); - - ClassWorld world = mock(ClassWorld.class); - when(world.newRealm(anyString(), any(ClassLoader.class))).thenThrow(new UnsupportedClassVersionError()); - - classloaders = new PluginClassloaders(getClass().getClassLoader(), world); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - classloaders.init(Arrays.asList(checkstyle)); - } - - private File getFile(String filename) { - return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java deleted file mode 100644 index b66bf864085..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.core.plugins; - -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PluginJarInstallerTest { - - private PluginJarInstaller extractor; - - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File userHome; - - @Before - public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new PluginJarInstaller() { - @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return null; - } - }; - } - - @Test - public void should_extract_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-cobertura-plugin-3.1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("cobertura"); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getName()).isEqualTo("Cobertura"); - assertThat(metadata.isCore()).isEqualTo(true); - assertThat(metadata.getFile().getName()).isEqualTo("sonar-cobertura-plugin-3.1.1.jar"); - assertThat(metadata.getVersion()).isEqualTo("3.1.1"); - assertThat(metadata.getImplementationBuild()).isEqualTo("b9283404030db9ce1529b1fadfb98331686b116d"); - assertThat(metadata.getHomepage()).isEqualTo("http://www.sonarsource.org/plugins/sonar-cobertura-plugin"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehaus.org/browse/SONAR"); - } - - @Test - public void should_read_sonar_version() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-switch-off-violations-plugin-1.1.jar"), false); - - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("2.5"); - } - - @Test - public void should_extract_extension_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("checkstyleextensions"); - assertThat(metadata.getBasePlugin()).isEqualTo("checkstyle"); - } - - @Test - public void should_extract_requires_plugin_information() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("fake2-plugin-1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("fake2"); - assertThat(metadata.getRequiredPlugins().get(0)).isEqualTo("fake1:1.1"); - } - - File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(PluginJarInstallerTest.class.getResource("/org/sonar/core/plugins/" + filename)); - File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); - FileUtils.copyFile(src, destFile); - return destFile; - } - -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java deleted file mode 100644 index e2f6dadd7a0..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ - -package org.sonar.core.plugins; - -import org.junit.Test; - -import java.net.URL; -import java.util.Arrays; -import java.util.List; - -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; - -public class ResourcesClassloaderTest { - - @Test - public void test() throws Exception { - List urls = Arrays.asList(new URL("http://localhost:9000/deploy/plugins/checkstyle/extension.xml")); - ResourcesClassloader classLoader = new ResourcesClassloader(urls, null); - assertThat(classLoader.findResource("extension.xml"), notNullValue()); - } -} diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar deleted file mode 100644 index 343ad65f133..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar and /dev/null differ diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar deleted file mode 100644 index 505311c008b..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar and /dev/null differ diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml b/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml deleted file mode 100644 index 75a263db3c3..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar deleted file mode 100644 index f4b8b79b776..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar and /dev/null differ diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar deleted file mode 100644 index 4ae5393cee5..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar and /dev/null differ diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar deleted file mode 100644 index 6a74b55d02c..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar and /dev/null differ diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar deleted file mode 100644 index 8044dff8988..00000000000 Binary files a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar and /dev/null differ diff --git a/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java b/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java index e5922bf75f0..358d4bbef40 100644 --- a/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java +++ b/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.api.charts; diff --git a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java index 3588a908b4b..63a1168639d 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java @@ -20,15 +20,12 @@ package org.sonar.home.cache; import org.apache.commons.io.FileUtils; -import org.sonar.api.utils.ZipUtils; import org.sonar.home.log.Log; import javax.annotation.CheckForNull; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.util.zip.ZipEntry; /** * This class is responsible for managing Sonar batch file cache. You can put file into cache and @@ -138,7 +135,7 @@ public class FileCache { } } - private File createTempDir() { + public File createTempDir() { String baseName = System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { @@ -161,43 +158,4 @@ public class FileCache { } return dir; } - - /** - * Unzip a cached file. Unzip is done only the first time. - * @param cachedFile - * @return directory where cachedFile was unzipped - * @throws IOException - */ - public File unzip(File cachedFile) throws IOException { - String filename = cachedFile.getName(); - File destDir = new File(cachedFile.getParentFile(), filename + "_unzip"); - File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock"); - if (!destDir.exists()) { - FileOutputStream out = new FileOutputStream(lockFile); - try { - java.nio.channels.FileLock lock = out.getChannel().lock(); - try { - // Recheck in case of concurrent processes - if (!destDir.exists()) { - File tempDir = createTempDir(); - ZipUtils.unzip(cachedFile, tempDir, new LibFilter()); - FileUtils.moveDirectory(tempDir, destDir); - } - } finally { - lock.release(); - } - } finally { - out.close(); - FileUtils.deleteQuietly(lockFile); - } - } - return destDir; - } - - private static final class LibFilter implements ZipUtils.ZipEntryFilter { - @Override - public boolean accept(ZipEntry entry) { - return entry.getName().startsWith("META-INF/lib"); - } - } } 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 be82711e85e..2d90503598d 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 @@ -28,7 +28,6 @@ import org.sonar.home.log.Slf4jLog; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; @@ -120,32 +119,4 @@ public class FileCacheTest { assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by"); } - @Test - public void unzip_from_cache() throws IOException, URISyntaxException { - final File samplePlugin = new File(this.getClass().getResource("/sonar-checkstyle-plugin-2.8.jar").toURI()); - FileHashes hashes = mock(FileHashes.class); - final FileCache cache = new FileCache(tempFolder.newFolder(), log, hashes); - when(hashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = new FileCache.Downloader() { - public void download(String filename, File toFile) throws IOException { - FileUtils.copyFile(samplePlugin, toFile); - } - }; - final File cachedFile = cache.get("sonar-checkstyle-plugin-2.8.jar", "ABCDE", downloader); - assertThat(cachedFile).isNotNull().exists().isFile(); - assertThat(cachedFile.getName()).isEqualTo("sonar-checkstyle-plugin-2.8.jar"); - - File pluginDepsDir = cache.unzip(cachedFile); - - assertThat(pluginDepsDir.listFiles()).hasSize(1); - File metaDir = new File(pluginDepsDir, "META-INF"); - assertThat(metaDir.listFiles()).hasSize(1); - File libDir = new File(metaDir, "lib"); - assertThat(libDir.listFiles()).hasSize(3); - assertThat(libDir.listFiles()).containsOnly(new File(libDir, "antlr-2.7.6.jar"), new File(libDir, "checkstyle-5.1.jar"), new File(libDir, "commons-cli-1.0.jar")); - - // Unzip again should not do anything as it is already unzipped - cache.unzip(cachedFile); - } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java index ea7bbc755e9..23f5021d3fb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java @@ -24,9 +24,9 @@ import java.util.List; /** * A plugin is a group of extensions. See org.sonar.api.Extension interface to browse * available extension points. - *

*

The manifest property Plugin-Class must declare the name of the implementation class. * It is automatically set by sonar-packaging-maven-plugin when building plugins.

+ *

Implementation must declare a public constructor with no-parameters.

* * @see org.sonar.api.Extension * @since 1.10 diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java deleted file mode 100644 index e8555ed4e35..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import com.google.common.collect.Iterables; -import org.picocontainer.Characteristics; -import org.picocontainer.ComponentAdapter; -import org.picocontainer.DefaultPicoContainer; -import org.picocontainer.MutablePicoContainer; -import org.picocontainer.behaviors.OptInCaching; -import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; -import org.picocontainer.monitors.NullComponentMonitor; -import org.sonar.api.BatchComponent; -import org.sonar.api.ServerComponent; -import org.sonar.api.config.PropertyDefinitions; - -import javax.annotation.Nullable; - -import java.util.Collection; -import java.util.List; - -/** - * @since 2.12 - */ -public class ComponentContainer implements BatchComponent, ServerComponent { - - // no need for multiple children - ComponentContainer parent, child; - MutablePicoContainer pico; - PropertyDefinitions propertyDefinitions; - ComponentKeys componentKeys; - - /** - * Create root container - */ - public ComponentContainer() { - this.parent = null; - this.child = null; - this.pico = createPicoContainer(); - this.componentKeys = new ComponentKeys(); - propertyDefinitions = new PropertyDefinitions(); - addSingleton(propertyDefinitions); - addSingleton(this); - } - - /** - * Create child container - */ - protected ComponentContainer(ComponentContainer parent) { - this.parent = parent; - this.pico = parent.pico.makeChildContainer(); - this.parent.child = this; - this.propertyDefinitions = parent.propertyDefinitions; - this.componentKeys = new ComponentKeys(); - addSingleton(this); - } - - public void execute() { - boolean threw = true; - try { - startComponents(); - threw = false; - } finally { - stopComponents(threw); - } - } - - /** - * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting - * a component twice is not authorized. - */ - public ComponentContainer startComponents() { - try { - doBeforeStart(); - pico.start(); - doAfterStart(); - return this; - } catch (Exception e) { - throw PicoUtils.propagate(e); - } - } - - /** - * This method aims to be overridden - */ - protected void doBeforeStart() { - // nothing - } - - /** - * This method aims to be overridden - */ - protected void doAfterStart() { - // nothing - } - - /** - * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting - * a component twice is not authorized. - */ - public ComponentContainer stopComponents() { - return stopComponents(false); - } - - public ComponentContainer stopComponents(boolean swallowException) { - try { - pico.stop(); - pico.dispose(); - - } catch (RuntimeException e) { - if (!swallowException) { - throw PicoUtils.propagate(e); - } - } finally { - removeChild(); - if (parent != null) { - parent.removeChild(); - } - } - return this; - } - - /** - * @since 3.5 - */ - public ComponentContainer add(Object... objects) { - for (Object object : objects) { - if (object instanceof ComponentAdapter) { - addPicoAdapter((ComponentAdapter) object); - } else if (object instanceof Iterable) { - add(Iterables.toArray((Iterable) object, Object.class)); - } else { - addSingleton(object); - } - } - return this; - } - - public ComponentContainer addSingletons(Collection components) { - for (Object component : components) { - addSingleton(component); - } - return this; - } - - public ComponentContainer addSingleton(Object component) { - return addComponent(component, true); - } - - /** - * @param singleton return always the same instance if true, else a new instance - * is returned each time the component is requested - */ - public ComponentContainer addComponent(Object component, boolean singleton) { - Object key = componentKeys.of(component); - if (component instanceof ComponentAdapter) { - pico.addAdapter((ComponentAdapter) component); - } else { - try { - pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component); - } catch (Throwable t) { - throw new IllegalStateException("Unable to register component " + getName(component), t); - } - declareExtension(null, component); - } - return this; - } - - public ComponentContainer addExtension(@Nullable PluginMetadata plugin, Object extension) { - Object key = componentKeys.of(extension); - try { - pico.as(Characteristics.CACHE).addComponent(key, extension); - } catch (Throwable t) { - throw new IllegalStateException("Unable to register extension " + getName(extension), t); - } - declareExtension(plugin, extension); - return this; - } - - private String getName(Object extension) { - if (extension instanceof Class) { - return ((Class) extension).getName(); - } - return getName(extension.getClass()); - } - - public void declareExtension(@Nullable PluginMetadata plugin, Object extension) { - propertyDefinitions.addComponent(extension, plugin != null ? plugin.getName() : ""); - } - - public ComponentContainer addPicoAdapter(ComponentAdapter adapter) { - pico.addAdapter(adapter); - return this; - } - - public T getComponentByType(Class tClass) { - return pico.getComponent(tClass); - } - - public Object getComponentByKey(Object key) { - return pico.getComponent(key); - } - - public List getComponentsByType(Class tClass) { - return pico.getComponents(tClass); - } - - public ComponentContainer removeChild() { - if (child != null) { - pico.removeChildContainer(child.pico); - child = null; - } - return this; - } - - public ComponentContainer createChild() { - return new ComponentContainer(this); - } - - static MutablePicoContainer createPicoContainer() { - ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close"); - return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); - } - - public ComponentContainer getParent() { - return parent; - } - - public ComponentContainer getChild() { - return child; - } - - public MutablePicoContainer getPicoContainer() { - return pico; - } -} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java deleted file mode 100644 index 56de51f17e1..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import com.google.common.annotations.VisibleForTesting; -import org.sonar.api.utils.internal.Uuids; -import org.sonar.api.utils.log.Logger; -import org.sonar.api.utils.log.Loggers; - -import java.util.HashSet; -import java.util.Set; -import java.util.regex.Pattern; - -/** - * @since 3.7.1 - */ -class ComponentKeys { - - private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(".+@[a-f0-9]+"); - private final Set objectsWithoutToString = new HashSet(); - - Object of(Object component) { - return of(component, Loggers.get(ComponentKeys.class)); - } - - @VisibleForTesting - Object of(Object component, Logger log) { - if (component instanceof Class) { - return component; - } - String key = component.toString(); - if (IDENTITY_HASH_PATTERN.matcher(key).matches()) { - if (!objectsWithoutToString.add(component.getClass())) { - log.warn(String.format("Bad component key: %s. Please implement toString() method on class %s", key, component.getClass().getName())); - } - key += Uuids.create(); - } - return new StringBuilder().append(component.getClass().getCanonicalName()).append("-").append(key).toString(); - } -} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java deleted file mode 100644 index b14d886196b..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java +++ /dev/null @@ -1,46 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import com.google.common.base.Throwables; -import org.picocontainer.PicoLifecycleException; - -class PicoUtils { - - private PicoUtils() { - } - - static Throwable sanitize(Throwable t) { - Throwable result = t; - Throwable cause = t.getCause(); - if (t instanceof PicoLifecycleException && cause != null) { - if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) { - result = cause.getCause(); - } else { - result = cause; - } - } - return result; - } - - static RuntimeException propagate(Throwable t) { - throw Throwables.propagate(sanitize(t)); - } -} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java deleted file mode 100644 index 97ab71bcfc1..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import javax.annotation.CheckForNull; -import java.io.File; -import java.util.List; - -/** - * @since 2.8 - */ -public interface PluginMetadata { - File getFile(); - - List getDeployedFiles(); - - String getKey(); - - String getName(); - - String getMainClass(); - - String getDescription(); - - String getOrganization(); - - String getOrganizationUrl(); - - String getLicense(); - - String getVersion(); - - String getHomepage(); - - /** - * @since 3.6 - */ - String getIssueTrackerUrl(); - - boolean isUseChildFirstClassLoader(); - - String getBasePlugin(); - - /** - * Always return null since version 5.2 - * @deprecated in 5.2. Concept of parent relationship is removed. See https://jira.codehaus.org/browse/SONAR-6433 - */ - @Deprecated - @CheckForNull - String getParent(); - - List getRequiredPlugins(); - - boolean isCore(); - - String getImplementationBuild(); -} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java deleted file mode 100644 index ad26ec2dae3..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java +++ /dev/null @@ -1,48 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import org.sonar.api.BatchComponent; -import org.sonar.api.Plugin; -import org.sonar.api.ServerComponent; - -import javax.annotation.CheckForNull; -import java.util.Collection; - -public interface PluginRepository extends BatchComponent, ServerComponent { - @CheckForNull - Plugin getPlugin(String key); - - /** - * Metadata of installed plugins. Metadata includes all the fields available in update center - * (plugin key, name, version, description, license, ...) and some technical information like - * list of embedded libraries and classloader strategy. - * - * @since 2.9 - */ - Collection getMetadata(); - - /** - * Search for an installed plugin. Returns null if the plugin is not installed. - * @since 2.9 - */ - @CheckForNull - PluginMetadata getMetadata(String pluginKey); -} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java deleted file mode 100644 index 8cbe1f5d3b4..00000000000 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java +++ /dev/null @@ -1,390 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.picocontainer.injectors.ProviderAdapter; -import org.sonar.api.Property; -import org.sonar.api.config.PropertyDefinitions; - -import java.util.Arrays; - -import static junit.framework.Assert.assertTrue; -import static junit.framework.Assert.fail; -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -public class ComponentContainerTest { - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Test - public void shouldRegisterItself() { - ComponentContainer container = new ComponentContainer(); - assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container); - } - - @Test - public void should_start_and_stop() { - ComponentContainer container = spy(new ComponentContainer()); - container.addSingleton(StartableComponent.class); - container.startComponents(); - - assertThat(container.getComponentByType(StartableComponent.class).started).isTrue(); - assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse(); - verify(container).doBeforeStart(); - verify(container).doAfterStart(); - - container.stopComponents(); - assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue(); - } - - @Test - public void should_start_and_stop_hierarchy_of_containers() { - StartableComponent parentComponent = new StartableComponent(); - final StartableComponent childComponent = new StartableComponent(); - ComponentContainer parentContainer = new ComponentContainer() { - @Override - public void doAfterStart() { - ComponentContainer childContainer = new ComponentContainer(this); - childContainer.add(childComponent); - childContainer.execute(); - } - }; - parentContainer.add(parentComponent); - parentContainer.execute(); - assertThat(parentComponent.started).isTrue(); - assertThat(parentComponent.stopped).isTrue(); - assertThat(childComponent.started).isTrue(); - assertThat(childComponent.stopped).isTrue(); - } - - @Test - public void should_stop_hierarchy_of_containers_on_failure() { - StartableComponent parentComponent = new StartableComponent(); - final StartableComponent childComponent1 = new StartableComponent(); - final UnstartableComponent childComponent2 = new UnstartableComponent(); - ComponentContainer parentContainer = new ComponentContainer() { - @Override - public void doAfterStart() { - ComponentContainer childContainer = new ComponentContainer(this); - childContainer.add(childComponent1); - childContainer.add(childComponent2); - childContainer.execute(); - } - }; - parentContainer.add(parentComponent); - try { - parentContainer.execute(); - fail(); - } catch (Exception e) { - assertThat(parentComponent.started).isTrue(); - assertThat(parentComponent.stopped).isTrue(); - assertThat(childComponent1.started).isTrue(); - assertThat(childComponent1.stopped).isTrue(); - } - } - - @Test - public void testChild() { - ComponentContainer parent = new ComponentContainer(); - parent.startComponents(); - - ComponentContainer child = parent.createChild(); - child.addSingleton(StartableComponent.class); - child.startComponents(); - - assertThat(child.getParent()).isSameAs(parent); - assertThat(parent.getChild()).isSameAs(child); - assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child); - assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent); - assertThat(child.getComponentByType(StartableComponent.class)).isNotNull(); - assertThat(parent.getComponentByType(StartableComponent.class)).isNull(); - - parent.stopComponents(); - } - - @Test - public void testRemoveChild() { - ComponentContainer parent = new ComponentContainer(); - parent.startComponents(); - - ComponentContainer child = parent.createChild(); - assertThat(parent.getChild()).isSameAs(child); - - parent.removeChild(); - assertThat(parent.getChild()).isNull(); - } - - @Test - public void shouldForwardStartAndStopToDescendants() { - ComponentContainer grandParent = new ComponentContainer(); - ComponentContainer parent = grandParent.createChild(); - ComponentContainer child = parent.createChild(); - child.addSingleton(StartableComponent.class); - - grandParent.startComponents(); - - StartableComponent component = child.getComponentByType(StartableComponent.class); - assertTrue(component.started); - - parent.stopComponents(); - assertTrue(component.stopped); - } - - @Test - public void shouldDeclareComponentProperties() { - ComponentContainer container = new ComponentContainer(); - container.addSingleton(ComponentWithProperty.class); - - PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); - assertThat(propertyDefinitions.get("foo")).isNotNull(); - assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar"); - } - - @Test - public void shouldDeclareExtensionWithoutAddingIt() { - ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); - container.declareExtension(plugin, ComponentWithProperty.class); - - PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); - assertThat(propertyDefinitions.get("foo")).isNotNull(); - assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull(); - } - - @Test - public void shouldDeclareExtensionWhenAdding() { - ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); - container.addExtension(plugin, ComponentWithProperty.class); - - PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); - assertThat(propertyDefinitions.get("foo")).isNotNull(); - assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); - assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull(); - } - - @Test - public void test_add_class() { - ComponentContainer container = new ComponentContainer(); - container.add(ComponentWithProperty.class, SimpleComponent.class); - assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); - assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); - } - - @Test - public void test_add_collection() { - ComponentContainer container = new ComponentContainer(); - container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class)); - assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); - assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); - } - - @Test - public void test_add_adapter() { - ComponentContainer container = new ComponentContainer(); - container.add(new SimpleComponentProvider()); - assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); - } - - @Test - public void should_sanitize_pico_exception_on_start_failure() { - ComponentContainer container = new ComponentContainer(); - container.add(UnstartableComponent.class); - - // do not expect a PicoException - thrown.expect(IllegalStateException.class); - container.startComponents(); - } - - @Test - public void display_plugin_name_when_failing_to_add_extension() { - ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); - - container.startComponents(); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Unable to register extension org.sonar.api.platform.ComponentContainerTest$UnstartableComponent"); - - container.addExtension(plugin, UnstartableComponent.class); - - } - - @Test - public void test_start_failure() { - ComponentContainer container = new ComponentContainer(); - StartableComponent startable = new StartableComponent(); - container.add(startable, UnstartableComponent.class); - - try { - container.execute(); - fail(); - } catch (Exception e) { - assertThat(startable.started).isTrue(); - - // container stops the components that have already been started - assertThat(startable.stopped).isTrue(); - } - } - - @Test - public void test_stop_failure() { - ComponentContainer container = new ComponentContainer(); - StartableComponent startable = new StartableComponent(); - container.add(startable, UnstoppableComponent.class); - - try { - container.execute(); - fail(); - } catch (Exception e) { - assertThat(startable.started).isTrue(); - - // container should stop the components that have already been started - // ... but that's not the case - } - } - - @Test - public void stop_exception_should_not_hide_start_exception() { - ComponentContainer container = new ComponentContainer(); - container.add(UnstartableComponent.class, UnstoppableComponent.class); - - thrown.expect(IllegalStateException.class); - thrown.expectMessage("Fail to start"); - container.execute(); - } - - @Test - public void should_execute_components() { - ComponentContainer container = new ComponentContainer(); - StartableComponent component = new StartableComponent(); - container.add(component); - - container.execute(); - - assertThat(component.started).isTrue(); - assertThat(component.stopped).isTrue(); - } - - /** - * Method close() must be called even if the methods start() or stop() - * are not defined. - */ - @Test - public void should_close_components_without_lifecycle() { - ComponentContainer container = new ComponentContainer(); - CloseableComponent component = new CloseableComponent(); - container.add(component); - - container.execute(); - - assertThat(component.isClosed).isTrue(); - } - - /** - * Method close() must be executed after stop() - */ - @Test - public void should_close_components_with_lifecycle() { - ComponentContainer container = new ComponentContainer(); - StartableCloseableComponent component = new StartableCloseableComponent(); - container.add(component); - - container.execute(); - - assertThat(component.isStopped).isTrue(); - assertThat(component.isClosed).isTrue(); - assertThat(component.isClosedAfterStop).isTrue(); - } - - public static class StartableComponent { - public boolean started = false, stopped = false; - - public void start() { - started = true; - } - - public void stop() { - stopped = true; - } - } - - public static class UnstartableComponent { - public void start() { - throw new IllegalStateException("Fail to start"); - } - - public void stop() { - - } - } - - public static class UnstoppableComponent { - public void start() { - } - - public void stop() { - throw new IllegalStateException("Fail to stop"); - } - } - - @Property(key = "foo", defaultValue = "bar", name = "Foo") - public static class ComponentWithProperty { - - } - - public static class SimpleComponent { - - } - - public static class SimpleComponentProvider extends ProviderAdapter { - public SimpleComponent provide() { - return new SimpleComponent(); - } - } - - public static class CloseableComponent implements AutoCloseable { - public boolean isClosed = false; - - @Override - public void close() throws Exception { - isClosed = true; - } - } - - public static class StartableCloseableComponent implements AutoCloseable { - public boolean isClosed = false, isStopped = false, isClosedAfterStop = false; - - public void stop() { - isStopped = true; - } - - @Override - public void close() throws Exception { - isClosed = true; - isClosedAfterStop = isStopped; - } - } -} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java deleted file mode 100644 index f2b36a677fe..00000000000 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import org.junit.Test; -import org.sonar.api.utils.log.Logger; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; - -public class ComponentKeysTest { - - ComponentKeys keys = new ComponentKeys(); - - @Test - public void generate_key_of_class() { - assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class); - } - - @Test - public void generate_key_of_object() { - assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.api.platform.ComponentKeysTest.FakeComponent-fake"); - } - - @Test - public void should_log_warning_if_toString_is_not_overridden() { - Logger log = mock(Logger.class); - keys.of(new Object(), log); - verifyZeroInteractions(log); - - // only on non-first runs, to avoid false-positives on singletons - keys.of(new Object(), log); - verify(log).warn(startsWith("Bad component key")); - } - - @Test - public void should_generate_unique_key_when_toString_is_not_overridden() { - Object key = keys.of(new WrongToStringImpl()); - assertThat(key).isNotEqualTo(WrongToStringImpl.KEY); - - Object key2 = keys.of(new WrongToStringImpl()); - assertThat(key2).isNotEqualTo(key); - } - - static class FakeComponent { - @Override - public String toString() { - return "fake"; - } - } - - static class WrongToStringImpl { - static final String KEY = "my.Component@123a"; - - @Override - public String toString() { - return KEY; - } - } -} diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java b/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java deleted file mode 100644 index d5b74341b8b..00000000000 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java +++ /dev/null @@ -1,106 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.api.platform; - -import org.junit.Test; -import org.picocontainer.Characteristics; -import org.picocontainer.MutablePicoContainer; -import org.picocontainer.PicoLifecycleException; - -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.Assert.fail; - - -public class PicoUtilsTest { - @Test - public void shouldSanitizePicoLifecycleException() { - Throwable th = PicoUtils.sanitize(newPicoLifecycleException(false)); - - assertThat(th).isInstanceOf(IllegalStateException.class); - assertThat(th.getMessage()).isEqualTo("A good reason to fail"); - } - - @Test - public void shouldSanitizePicoLifecycleException_no_wrapper_message() { - Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg"))); - - assertThat(th).isInstanceOf(IllegalStateException.class); - assertThat(th.getMessage()).isEqualTo("msg"); - } - - @Test - public void shouldNotSanitizeOtherExceptions() { - Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo")); - - assertThat(th).isInstanceOf(IllegalArgumentException.class); - assertThat(th.getMessage()).isEqualTo("foo"); - } - - @Test - public void shouldPropagateInitialUncheckedException() { - try { - PicoUtils.propagate(newPicoLifecycleException(false)); - fail(); - } catch (RuntimeException e) { - assertThat(e).isInstanceOf(IllegalStateException.class); - } - } - - @Test - public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() { - try { - PicoUtils.propagate(newPicoLifecycleException(true)); - fail(); - } catch (RuntimeException e) { - assertThat(e.getCause()).isInstanceOf(IOException.class); - assertThat(e.getCause().getMessage()).isEqualTo("Checked"); - } - } - - private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) { - MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE); - if (initialCheckedException) { - container.addComponent(CheckedFailureComponent.class); - } else { - container.addComponent(UncheckedFailureComponent.class); - } - try { - container.start(); - return null; - - } catch (PicoLifecycleException e) { - return e; - } - } - - public static class UncheckedFailureComponent { - public void start() { - throw new IllegalStateException("A good reason to fail"); - } - } - - public static class CheckedFailureComponent { - public void start() throws IOException { - throw new IOException("Checked"); - } - } -}