diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-04-24 09:15:05 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-05-11 10:21:55 +0200 |
commit | 14a5c982e5f1b28354a853073bd3e225b3914abe (patch) | |
tree | e298a2948f49628880f8d5290451adc14a920613 | |
parent | cba928d505985972e13c8e895b490a52702af925 (diff) | |
download | sonarqube-14a5c982e5f1b28354a853073bd3e225b3914abe.tar.gz sonarqube-14a5c982e5f1b28354a853073bd3e225b3914abe.zip |
SONAR-6370 isolate plugin classloader from core
161 files changed, 3457 insertions, 3110 deletions
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; @@ -597,6 +597,11 @@ </dependency> <dependency> <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-classloader</artifactId> + <version>1.0</version> + </dependency> + <dependency> + <groupId>org.codehaus.sonar</groupId> <artifactId>sonar-markdown</artifactId> <version>${project.version}</version> </dependency> @@ -708,11 +713,6 @@ </exclusions> </dependency> <dependency> - <groupId>org.codehaus.plexus</groupId> - <artifactId>plexus-classworlds</artifactId> - <version>2.5.1</version> - </dependency> - <dependency> <groupId>com.tinkerpop.blueprints</groupId> <artifactId>blueprints-core</artifactId> <version>2.2.0</version> 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 @@ <directory>src/main/resources</directory> <filtering>true</filtering> </resource> + </resources> + <testResources> + <testResource> + <directory>src/test/resources</directory> + <filtering>false</filtering> + </testResource> + <testResource> + <directory>src/test/projects</directory> + <filtering>false</filtering> + </testResource> + + </testResources> <plugins> <plugin> 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 "<pluginKey>-model.xml". * </p> */ -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<File> getCorePlugins() { - File corePluginsDir = new File(getHomeDir(), "lib/core-plugins"); - return getFiles(corePluginsDir, "jar"); - } - - public List<File> getBundledPlugins() { - File corePluginsDir = new File(getHomeDir(), "lib/bundled-plugins"); - return getFiles(corePluginsDir, "jar"); + public File getInstalledPluginsDir() { + return new File(getHomeDir(), "extensions/plugins"); } - public List<File> 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<File> getFiles(File dir, String... fileSuffixes) { - List<File> files = new ArrayList<File>(); + List<File> 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<String, String>() { @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<String, Object> attributes() { LinkedHashMap<String, Object> attributes = new LinkedHashMap<>(); - for (PluginMetadata plugin : plugins()) { + for (PluginInfo plugin : plugins()) { LinkedHashMap<String, Object> 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<PluginMetadata> plugins() { - return Lists.newArrayList(Iterables.filter(repository.getMetadata(), new Predicate<PluginMetadata>() { + private Iterable<PluginInfo> plugins() { + return Iterables.filter(repository.getPluginInfos(), new Predicate<PluginInfo>() { @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<DefaultPluginMetadata> getDownloadedPlugins() { - return newArrayList(transform(listPlugins(this.downloadDir), installer.fileToPlugin())); + public Collection<PluginInfo> 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<File> listTempFile(File dir) { - return FileUtils.listFiles(dir, new String[]{TMP_SUFFIX}, false); + return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false); } private static Collection<File> 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<PluginMetadata> metadata) { + public static PluginReferential getInstalledPluginReferential(Collection<PluginInfo> metadata) { List<PluginManifest> pluginManifestList = getPluginManifestList(metadata); return PluginReferentialManifestConverter.fromPluginManifests(pluginManifestList); } - private static List<PluginManifest> getPluginManifestList(Collection<PluginMetadata> metadata) { + private static List<PluginManifest> getPluginManifestList(Collection<PluginInfo> metadata) { List<PluginManifest> 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<PluginMetadata, Object> installedExtensionsByPlugin = ArrayListMultimap.create(); + ListMultimap<PluginInfo, Object> 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<PluginMetadata, Object> entry : installedExtensionsByPlugin.entries()) { - PluginMetadata plugin = entry.getKey(); + for (Map.Entry<PluginInfo, Object> 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<String, PluginMetadata> pluginByKeys = Maps.newHashMap(); - private final ServerUpgradeStatus serverUpgradeStatus; - private static final Set<String> BLACKLISTED_PLUGINS = new HashSet<String>(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<String> 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<DefaultPluginMetadata> 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<PluginMetadata> getMetadata() { - return pluginByKeys.values(); - } - - public PluginMetadata getMetadata(String pluginKey) { - return pluginByKeys.get(pluginKey); - } - - private PluginReferential getPluginReferential() { - return PluginReferentialMetadataConverter.getInstalledPluginReferential(getMetadata()); - } - - private static Collection<File> listJarFiles(File pluginDir) { - return listFiles(pluginDir, new String[] {FILE_EXTENSION_JAR}, false); - } - - private enum FileToName implements Function<File, String> { - 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: + * <ul> + * <li>installation of bundled plugins on first server startup</li> + * <li>installation of new plugins (effective after server startup)</li> + * <li>un-installation of plugins (effective after server startup)</li> + * <li>cancel pending installations/un-installations</li> + * <li>load plugin bytecode</li> + * </ul> */ public class ServerPluginRepository implements PluginRepository, Startable { - private final ServerPluginJarsInstaller jarsInstaller; - private final PluginClassloaders classloaders; - private Map<String, Plugin> pluginsByKey; + private static final Logger LOG = Loggers.get(ServerPluginRepository.class); + private static final String FILE_EXTENSION_JAR = "jar"; + private static final Set<String> 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<String> 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<String, PluginInfo> pluginInfosByKeys = new HashMap<>(); + private final Map<String, Plugin> 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<String> keys) { + this.blacklistedPluginKeys = keys; } @Override public void start() { - jarsInstaller.install(); - Collection<PluginMetadata> 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<String> 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<PluginInfo> 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<String> 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<PluginInfo> 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<String, PluginInfo> getPluginInfosByKeys() { + return pluginInfosByKeys; + } + + @Override + public Collection<PluginInfo> 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<PluginMetadata> 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<File, String> { + 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<File> 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<PluginMetadata> pluginMetadatas = retrieveAndSortPluginMetadata(); + Collection<PluginInfo> infos = retrieveAndSortPluginMetadata(); JsonWriter jsonWriter = response.newJsonWriter(); jsonWriter.setSerializeEmptys(false); jsonWriter.beginObject(); - writeMetadataList(jsonWriter, pluginMetadatas); + writeMetadataList(jsonWriter, infos); jsonWriter.endObject(); jsonWriter.close(); } - private SortedSet<PluginMetadata> retrieveAndSortPluginMetadata() { + private SortedSet<PluginInfo> 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<PluginMetadata> pluginMetadatas) { + private void writeMetadataList(JsonWriter jsonWriter, Collection<PluginInfo> 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<PluginMetadata> { + private enum NotCorePluginsPredicate implements Predicate<PluginInfo> { 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<DefaultPluginMetadata> plugins = pluginDownloader.getDownloadedPlugins(); - for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { + Collection<PluginInfo> 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<DefaultPluginMetadata> plugins = serverPluginJarsInstaller.getUninstalledPlugins(); - for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) { + Collection<PluginInfo> 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<PluginMetadata> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() + public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() .onResultOf(PluginMetadataToName.INSTANCE) .compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE)); public static final Comparator<Plugin> NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) @@ -70,23 +71,26 @@ public class PluginWSCommons { public static final Comparator<PluginUpdate> 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<PluginMetadata, String> { + private enum PluginMetadataToName implements Function<PluginInfo, String> { INSTANCE; @Override - public String apply(@Nonnull PluginMetadata input) { + public String apply(@Nonnull PluginInfo input) { return input.getName(); } } - private enum PluginMetadataToKey implements Function<PluginMetadata, String> { + private enum PluginMetadataToKey implements Function<PluginInfo, String> { 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<PluginMetadata> { - 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<String> 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<PluginMetadata> getPluginsMetadata() { - return get(PluginRepository.class).getMetadata(); + /** + * Used for WS api/updatecenter/installed_plugins, to be replaced by api/plugins/installed. + */ + public Collection<PluginInfo> getPluginInfos() { + return get(PluginRepository.class).getPluginInfos(); } public List<ViewProxy<Widget>> 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 @@ -38,20 +38,6 @@ public class DefaultServerFileSystemTest { public TemporaryFolder temp = new TemporaryFolder(); @Test - public void find_plugins() throws Exception { - List<File> 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<File> 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( new File(Resources.getResource(PATH + "shouldFindCheckstyleExtensions").toURI()), temp.newFolder(), null); 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.<PluginMetadata>emptyList()); + when(pluginRepository.getPluginInfos()).thenReturn(Collections.<PluginInfo>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/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<String, Object> 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<PluginMetadata> getMetadata() { - List<PluginMetadata> 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<DefaultPluginMetadata> 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/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java index 04aea1fb391..00d579118ee 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java @@ -17,33 +17,24 @@ * 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; +package org.sonar.server.plugins; -import com.google.common.collect.Lists; -import org.apache.commons.lang.StringUtils; +import org.apache.commons.io.FileUtils; -import java.net.URL; -import java.net.URLClassLoader; +import java.io.File; 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<URL> urls; - - public ResourcesClassloader(Collection<URL> urls, ClassLoader parent) { - super(new URL[] {}, parent); - this.urls = Lists.newArrayList(urls); - } +public class TestProjectUtils { - @Override - public URL findResource(String name) { - for (URL url : urls) { - if (StringUtils.endsWith(url.getPath(), name)) { - return url; - } + /** + * 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<File> jars = FileUtils.listFiles(target, new String[] {"jar"}, false); + if (jars == null || jars.size() != 1) { + throw new IllegalArgumentException("Test project is badly defined: " + dirName); } - return null; + 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.<PluginMetadata>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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>parent</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>pom</packaging> + <modules> + <module>test-base-plugin</module> + <module>test-base-plugin-v2</module> + <module>test-core-plugin</module> + <module>test-extend-plugin</module> + <module>test-libs-plugin</module> + <module>test-require-plugin</module> + <module>test-requirenew-plugin</module> + </modules> + +</project> 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.2-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Base Plugin</name> + <description>Simple standalone plugin. Used by other fake plugins.</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testbase</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..9bd9e0e0fc1 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-base-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Base Plugin</name> + <description>Simple standalone plugin. Used by other fake plugins.</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testbase</pluginKey> + <pluginClass>BasePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..2a6148ce7b6 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-core-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Core Plugin</name> + <description>Fake core plugin used by tests</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>core</pluginKey> + <pluginClass>CorePlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..62eba2aa80f --- /dev/null +++ b/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-extend-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Extend Plugin</name> + <description>Fake plugin that extends the plugin with key "base"</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testextend</pluginKey> + <pluginClass>ExtendPlugin</pluginClass> + <basePlugin>testbase</basePlugin> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..7a81fdf0cce --- /dev/null +++ b/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-libs-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Libs Plugin</name> + <description>Fake plugin that embeds some libraries</description> + + <dependencies> + <!-- embedded libs. Chosen because small ! --> + <dependency> + <groupId>commons-email</groupId> + <artifactId>commons-email</artifactId> + <version>20030310.165926</version> + </dependency> + <dependency> + <groupId>commons-daemon</groupId> + <artifactId>commons-daemon</artifactId> + <version>1.0.15</version> + </dependency> + + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testlibs</pluginKey> + <pluginClass>LibsPlugin</pluginClass> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..85e4772f474 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-require-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Require Plugin</name> + <description>This fake plugin depends on test-base-plugin</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testrequire</pluginKey> + <pluginClass>RequirePlugin</pluginClass> + <requirePlugins>testbase:0.1</requirePlugins> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..ac1f9f68e46 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar 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 @@ +<?xml version="1.0" encoding="UTF-8"?> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.codehaus.sonar.tests</groupId> + <artifactId>test-requirenew-plugin</artifactId> + <version>0.1-SNAPSHOT</version> + <packaging>sonar-plugin</packaging> + <name>Test Require New Plugin</name> + <description>This fake plugin requires a version of test-base-plugin that is not installed</description> + + <dependencies> + <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-plugin-api</artifactId> + <version>4.5.4</version> + <scope>provided</scope> + </dependency> + </dependencies> + <build> + <sourceDirectory>src</sourceDirectory> + <plugins> + <plugin> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-packaging-maven-plugin</artifactId> + <version>1.13</version> + <extensions>true</extensions> + <configuration> + <pluginKey>testrequire</pluginKey> + <pluginClass>RequirePlugin</pluginClass> + <requirePlugins>testbase:0.2</requirePlugins> + </configuration> + </plugin> + </plugins> + </build> + +</project> 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 Binary files differnew file mode 100644 index 00000000000..3437dfee71c --- /dev/null +++ b/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar 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 Binary files differdeleted file mode 100644 index e788522ba71..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 7bcf027151a..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 0eb5f40bb8c..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 3b3ed4b1b78..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index e788522ba71..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 636176b3092..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index b60ea353a21..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 2e0488cebdf..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index f35e77146cc..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index abb19c057b5..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 7bcf027151a..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index acf4fa40269..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 3b3ed4b1b78..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index b781205e0d0..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 11b72f4f8eb..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 8044dff8988..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 19533234582..00000000000 --- a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar +++ /dev/null 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/DefaultPluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java index 2687ebc56df..6e2c5886c60 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -19,13 +19,14 @@ */ 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.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; +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; @@ -33,26 +34,52 @@ 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; /** - * A {@link PluginsRepository} implementation that put downloaded plugins in a FS cache. + * Downloads the plugins installed on server and stores them in a local user cache + * (see {@link FileCacheProvider}). */ -public class DefaultPluginsRepository implements PluginsRepository { +public class BatchPluginInstaller implements PluginInstaller { - private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsRepository.class); + private static final Logger LOG = Loggers.get(BatchPluginInstaller.class); - private ServerClient server; - private FileCache fileCache; + private final ServerClient server; + private final FileCache fileCache; + private final BatchPluginPredicate pluginPredicate; - public DefaultPluginsRepository(FileCache fileCache, ServerClient server) { + public BatchPluginInstaller(ServerClient server, FileCache fileCache, BatchPluginPredicate pluginPredicate) { this.server = server; this.fileCache = fileCache; + this.pluginPredicate = pluginPredicate; } @Override - public File pluginFile(final RemotePlugin remote) { + public Map<String, PluginInfo> installRemotes() { + Map<String, PluginInfo> 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<String, Plugin> 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() { @@ -73,27 +100,24 @@ public class DefaultPluginsRepository implements PluginsRepository { } } - @Override - public List<RemotePlugin> pluginList() { + /** + * Gets information about the plugins installed on server (filename, checksum) + */ + @VisibleForTesting + List<RemotePlugin> 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<RemotePlugin> remoteLocations = Lists.newArrayList(); + List<RemotePlugin> result = Lists.newArrayList(); for (String row : rows) { - remoteLocations.add(RemotePlugin.unmarshal(row)); + result.add(RemotePlugin.unmarshal(row)); } - return remoteLocations; + return result; } catch (Exception e) { - throw new IllegalStateException("Fail to download plugins index: " + url, e); + throw new IllegalStateException("Fail to download list of plugins: " + url, e); } } - - @Override - public Map<PluginMetadata, SonarPlugin> localPlugins() { - return Collections.emptyMap(); - } - } 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<String>, 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<String> 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<String> mergeList = newArrayList(blacks); + mergeList.removeAll(whites); + return mergeList.isEmpty() || !mergeList.contains(pluginKey); + } + + Set<String> getWhites() { + return whites; + } + + Set<String> getBlacks() { + return blacks; + } + + static List<String> 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<String, Plugin> pluginInstancesByKeys; + private Map<String, PluginInfo> infosByKeys; - private PluginsRepository pluginsReferential; - private Map<String, Plugin> pluginsByKey; - private Map<String, PluginMetadata> 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<PluginMetadata, SonarPlugin> localPlugins = pluginsReferential.localPlugins(); - if (!localPlugins.isEmpty()) { - LOG.info("Install local plugins"); - for (Map.Entry<PluginMetadata, SonarPlugin> 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<RemotePlugin> 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<String, Plugin> 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<PluginMetadata> getMetadata() { - return metadataByKey.values(); + public Collection<PluginInfo> 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<PluginMetadata, Plugin> getPluginsByMetadata() { - Map<PluginMetadata, Plugin> result = Maps.newHashMap(); - for (Map.Entry<String, PluginMetadata> 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<String> 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<String> 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<String> 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/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<PluginMetadata, Plugin> 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<ExtensionProvider> 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<PluginMetadata, Plugin> 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/PluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java index 58473fd6af1..97eb513d4a6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java @@ -19,33 +19,24 @@ */ package org.sonar.batch.bootstrap; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.RemotePlugin; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.core.platform.PluginInfo; -import java.io.File; -import java.util.List; import java.util.Map; -/** - * Plugin referential. - * @since 4.4 - */ -public interface PluginsRepository { +public interface PluginInstaller extends BatchComponent { /** - * Return list of remote plugins to be installed + * 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 */ - List<RemotePlugin> pluginList(); + Map<String, PluginInfo> installRemotes(); /** - * Return location of a given plugin on the local FS. + * Used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester */ - File pluginFile(RemotePlugin remote); - - /** - * Return the list of local plugins to be installed - */ - Map<PluginMetadata, SonarPlugin> localPlugins(); - + Map<String, Plugin> installLocals(); } 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<String, String> bootstrapProperties = new HashMap<String, String>(); + private final Map<String, String> 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<RemotePlugin> pluginList = new ArrayList<RemotePlugin>(); - private Map<RemotePlugin, File> pluginFiles = new HashMap<RemotePlugin, File>(); - Map<PluginMetadata, SonarPlugin> localPlugins = new HashMap<PluginMetadata, SonarPlugin>(); - - @Override - public List<RemotePlugin> 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<PluginMetadata, SonarPlugin> localPlugins() { - return localPlugins; - } - - } - private static class FakeServerIssuesLoader implements ServerIssuesLoader { private List<ServerIssue> serverIssues = new ArrayList<>(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java index 8866cf7fc0f..cbd837c66c9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java @@ -17,33 +17,38 @@ * 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; +package org.sonar.batch.mediumtest; -import org.sonar.api.BatchComponent; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.PluginJarInstaller; -import org.sonar.home.cache.FileCache; +import org.sonar.api.Plugin; +import org.sonar.batch.bootstrap.PluginInstaller; +import org.sonar.core.platform.PluginInfo; import java.io.File; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; -public class BatchPluginJarInstaller extends PluginJarInstaller implements BatchComponent { +public class FakePluginInstaller implements PluginInstaller { - private FileCache cache; + private final Map<String, PluginInfo> infosByKeys = new HashMap<>(); + private final Map<String, Plugin> instancesByKeys = new HashMap<>(); - public BatchPluginJarInstaller(FileCache cache) { - this.cache = cache; + public FakePluginInstaller add(String pluginKey, File jarFile) { + infosByKeys.put(pluginKey, PluginInfo.create(jarFile)); + return this; } - public DefaultPluginMetadata installToCache(File pluginFile, boolean isCore) { - DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore); - install(metadata, null, pluginFile); - return metadata; + public FakePluginInstaller add(String pluginKey, Plugin instance) { + instancesByKeys.put(pluginKey, instance); + return this; } @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return cache.unzip(pluginFile); + public Map<String, PluginInfo> installRemotes() { + return infosByKeys; } + @Override + public Map<String, Plugin> 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/DefaultPluginsRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java index 57e940ab7ae..03c3707919b 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -36,7 +36,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class DefaultPluginsRepositoryTest { +public class BatchPluginInstallerTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @@ -44,34 +44,30 @@ public class DefaultPluginsRepositoryTest { @Rule public ExpectedException thrown = ExpectedException.none(); + FileCache fileCache = mock(FileCache.class); + BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class); + @Test - public void should_request_list_of_plugins() { - FileCache cache = mock(FileCache.class); + public void listRemotePlugins() { + ServerClient server = mock(ServerClient.class); - when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false"); - DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server); - - List<RemotePlugin> 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(); + when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,false\nsqale,false"); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + + List<RemotePlugin> remotePlugins = installer.listRemotePlugins(); + assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); } @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); + when(fileCache.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); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); - RemotePlugin plugin = new RemotePlugin("checkstyle", true) - .setFile("checkstyle-plugin.jar", "fakemd5_1"); - File file = downloader.pluginFile(plugin); + RemotePlugin remote = new RemotePlugin("checkstyle", true).setFile("checkstyle-plugin.jar", "fakemd5_1"); + File file = installer.download(remote); assertThat(file).isEqualTo(pluginJar); } @@ -83,6 +79,6 @@ public class DefaultPluginsRepositoryTest { ServerClient server = mock(ServerClient.class); doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); - new DefaultPluginsRepository(mock(FileCache.class), server).pluginList(); + new BatchPluginInstaller(server, fileCache, pluginPredicate).installRemotes(); } } 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/BatchPluginJarInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java index 2209c57431a..06a25148775 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java @@ -24,7 +24,9 @@ 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.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; @@ -32,35 +34,37 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; -public class BatchPluginJarInstallerTest { - - private BatchPluginJarInstaller extractor; +public class BatchPluginUnzipperTest { @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); + public static TemporaryFolder temp = new TemporaryFolder(); - private File userHome; + File userHome; + BatchPluginUnzipper underTest; @Before public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new BatchPluginJarInstaller(new FileCacheBuilder().setUserHome(userHome).build()); + userHome = temp.newFolder(); + FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build(); + underTest = new BatchPluginUnzipper(fileCache); } @Test - public void should_copy_and_extract_dependencies() throws IOException { + public void copy_and_extract_libs() throws IOException { File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - DefaultPluginMetadata metadata = extractor.installToCache(fileFromCache, true); + UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache)); - assertThat(metadata.getKey()).isEqualTo("checkstyle"); + 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 should_extract_only_dependencies() throws IOException { + public void extract_only_libs() throws IOException { File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - extractor.installToCache(fileFromCache, true); + 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(); @@ -68,7 +72,7 @@ public class BatchPluginJarInstallerTest { } File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginJarInstallerTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/" + filename)); + 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/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<PluginMetadata, Plugin> newPlugin(final Object... extensions) { - Map<PluginMetadata, Plugin> 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.<PluginMetadata, Plugin>of( - metadata, plugin - )); - - GlobalContainer container = spy(GlobalContainer.create(Collections.<String, String>emptyMap(), Lists.<Object>newArrayList(pluginRepository))); - doNothing().when(container).executeTask(Collections.<String, String>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/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar Binary files differindex f937399bec5..f937399bec5 100644 --- 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/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar 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 @@ -20,14 +20,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-classloader</artifactId> + </dependency> + <dependency> <groupId>${project.groupId}</groupId> <artifactId>sonar-plugin-api</artifactId> - <exclusions> - <exclusion> - <groupId>classworlds</groupId> - <artifactId>classworlds</artifactId> - </exclusion> - </exclusions> </dependency> <dependency> <groupId>org.mybatis</groupId> @@ -90,10 +88,6 @@ <artifactId>commons-dbutils</artifactId> </dependency> <dependency> - <groupId>org.codehaus.plexus</groupId> - <artifactId>plexus-classworlds</artifactId> - </dependency> - <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> </dependency> 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<String, String> 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<PluginMetadata> metadata = pluginRepository.getMetadata(); - if (metadata.isEmpty()) { + this.propertyToBundles = new HashMap<>(); + Collection<PluginInfo> 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<ClassLoader> classLoadersFromPlugin(PluginRepository pluginRepository) { - List<ClassLoader> 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<ClassLoader> 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<ClassLoader> allPluginClassloaders(PluginRepository pluginRepository) { + // accepted limitation: some plugins extend base plugins, sharing the same classloader, so + // there may be duplicated classloaders in the list. + List<ClassLoader> 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-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java index e8555ed4e35..868208b12a0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java @@ -17,7 +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. */ -package org.sonar.api.platform; +package org.sonar.core.platform; import com.google.common.collect.Iterables; import org.picocontainer.Characteristics; @@ -36,9 +36,6 @@ 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 @@ -183,14 +180,14 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return this; } - public ComponentContainer addExtension(@Nullable PluginMetadata plugin, Object extension) { + 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(plugin, extension); + declareExtension(pluginInfo, extension); return this; } @@ -201,8 +198,8 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return getName(extension.getClass()); } - public void declareExtension(@Nullable PluginMetadata plugin, Object extension) { - propertyDefinitions.addComponent(extension, plugin != null ? plugin.getName() : ""); + public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) { + propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : ""); } public ComponentContainer addPicoAdapter(ComponentAdapter adapter) { @@ -234,7 +231,7 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return new ComponentContainer(this); } - static MutablePicoContainer createPicoContainer() { + public static MutablePicoContainer createPicoContainer() { ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close"); return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java index 56de51f17e1..be315cd9cf7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java @@ -17,9 +17,8 @@ * 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; +package org.sonar.core.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; @@ -28,19 +27,15 @@ 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<Class> objectsWithoutToString = new HashSet<Class>(); + private final Set<Class> 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; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java index b14d886196b..ee4e1ae42a5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java @@ -17,7 +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. */ -package org.sonar.api.platform; +package org.sonar.core.platform; import com.google.common.base.Throwables; import org.picocontainer.PicoLifecycleException; @@ -25,6 +25,7 @@ import org.picocontainer.PicoLifecycleException; class PicoUtils { private PicoUtils() { + // only static methods } static Throwable sanitize(Throwable 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<PluginInfo> { + + 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<RequiredPlugin> 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<RequiredPlugin> 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<File, PluginInfo> { + 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, ...). + * <p/> + * Standard plugins have their own isolated classloader. Some others can extend a "base" plugin. + * In this case they share the same classloader then the base plugin. + * <p/> + * 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<String, String> mainClassesByPluginKey = new HashMap<>(); + final List<File> files = new ArrayList<>(); + final Mask mask = new Mask(); + boolean selfFirstStrategy = false; + ClassLoader classloader = null; + + public ClassloaderDef(String basePluginKey) { + this.basePluginKey = basePluginKey; + } + } + + private final PluginUnzipper unzipper; + + public PluginLoader(PluginUnzipper unzipper) { + this.unzipper = unzipper; + } + + public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) { + Collection<ClassloaderDef> 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<String, ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) { + Map<String, ClassloaderDef> 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<ClassloaderDef> defs) { + ClassloaderBuilder builder = new ClassloaderBuilder(); + for (ClassloaderDef def : defs) { + builder + .newClassloader(def.basePluginKey, getClass().getClassLoader()) + .setExportMask(def.basePluginKey, def.mask) + .setLoadingOrder(def.basePluginKey, def.selfFirstStrategy ? SELF_FIRST : LoadingOrder.PARENT_FIRST); + for (File file : def.files) { + builder.addURL(def.basePluginKey, fileToUrl(file)); + } + } + Map<String, ClassLoader> 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<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) { + // instantiate plugins + Map<String, Plugin> instancesByPluginKey = new HashMap<>(); + for (ClassloaderDef def : defs) { + // the same classloader can be used by multiple plugins + for (Map.Entry<String, String> entry : def.mainClassesByPluginKey.entrySet()) { + 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<Plugin> 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<String, PluginInfo> 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-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java index ad26ec2dae3..1b3b170a938 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java @@ -17,32 +17,27 @@ * 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; +package org.sonar.core.platform; import org.sonar.api.BatchComponent; import org.sonar.api.Plugin; import org.sonar.api.ServerComponent; -import javax.annotation.CheckForNull; import java.util.Collection; +/** + * Provides information about the plugins installed in the dependency injection container + */ 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<PluginMetadata> getMetadata(); + Collection<PluginInfo> getPluginInfos(); + + PluginInfo getPluginInfo(String key); /** - * Search for an installed plugin. Returns null if the plugin is not installed. - * @since 2.9 + * @return the instance of {@link Plugin} for the given plugin key. Never return null. */ - @CheckForNull - PluginMetadata getMetadata(String pluginKey); + Plugin getPluginInstance(String key); + + boolean hasPlugin(String key); } diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java index e2f6dadd7a0..5ce1ca08da7 100644 --- a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java @@ -17,24 +17,24 @@ * 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; -package org.sonar.core.plugins; +import org.sonar.api.utils.ZipUtils; -import org.junit.Test; +import java.util.zip.ZipEntry; -import java.net.URL; -import java.util.Arrays; -import java.util.List; +public abstract class PluginUnzipper { -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; + protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib"; -public class ResourcesClassloaderTest { + public abstract UnzippedPlugin unzip(PluginInfo info); - @Test - public void test() throws Exception { - List<URL> 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()); + 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<File> libs; + + public UnzippedPlugin(String key, File main, Collection<File> libs) { + this.key = key; + this.main = main; + this.libs = libs; + } + + public String getKey() { + return key; + } + + public File getMain() { + return main; + } + + public Collection<File> getLibs() { + return libs; + } + + public static UnzippedPlugin createFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) { + File libDir = new File(unzippedDir, PluginUnzipper.LIB_RELATIVE_PATH_IN_JAR); + Collection<File> libs; + if (libDir.isDirectory() && libDir.exists()) { + libs = listFiles(libDir, null, false); + } else { + libs = Collections.emptyList(); + } + return new UnzippedPlugin(pluginKey, jarFile, libs); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java index 66a4069fba1..d93af63a5ba 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java @@ -17,13 +17,11 @@ * 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() { +/** + * 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<PluginMetadata> { - private File file; - private List<File> deployedFiles; - private List<String> 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<String> 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<File> getDeployedFiles() { - return deployedFiles; - } - - public DefaultPluginMetadata addDeployedFile(File f) { - this.deployedFiles.add(f); - return this; - } - - public List<String> getPathsToInternalDeps() { - return ImmutableList.copyOf(pathsToInternalDeps); - } - - public DefaultPluginMetadata setPathsToInternalDeps(List<String> 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<String> getRequiredPlugins() { - return ImmutableList.copyOf(requiredPlugins); - } - - public DefaultPluginMetadata setRequiredPlugins(List<String> 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 <code>true</code> 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}. - * <p/> - * <h3>IMPORTANT</h3> - * <p> - * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible - * for all other plugins even if they are located in dependent library. - * </p> - * <p/> - * <h4>Search order for {@link ClassRealm} :</h4> - * <ul> - * <li>parent class loader (passed via the constructor) if there is one</li> - * <li>imports</li> - * <li>realm's constituents</li> - * <li>parent realm</li> - * </ul> - */ -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<String, Plugin> init(Collection<PluginMetadata> plugins) { - List<PluginMetadata> 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<String, Plugin> 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<URL> resources = Lists.newArrayList(); - List<URL> 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<File, DefaultPluginMetadata> fileToPlugin() { - return jarFileToPlugin; - } - - public Function<File, DefaultPluginMetadata> fileToCorePlugin() { - return jarFileToCorePlugin; - } - - private final Function<File, DefaultPluginMetadata> jarFileToCorePlugin = new Function<File, DefaultPluginMetadata>() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, true); - } - }; - private final Function<File, DefaultPluginMetadata> jarFileToPlugin = new Function<File, DefaultPluginMetadata>() { - @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/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<PluginMetadata> plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); - when(pluginRepository.getMetadata()).thenReturn(plugins); + List<PluginInfo> 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-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java index 8cbe1f5d3b4..5d7923d23bd 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java @@ -17,7 +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. */ -package org.sonar.api.platform; +package org.sonar.core.platform; import org.junit.Rule; import org.junit.Test; @@ -31,7 +31,9 @@ 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.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; public class ComponentContainerTest { @@ -165,7 +167,7 @@ public class ComponentContainerTest { @Test public void shouldDeclareExtensionWithoutAddingIt() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.declareExtension(plugin, ComponentWithProperty.class); PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); @@ -176,7 +178,7 @@ public class ComponentContainerTest { @Test public void shouldDeclareExtensionWhenAdding() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.addExtension(plugin, ComponentWithProperty.class); PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); @@ -221,12 +223,12 @@ public class ComponentContainerTest { @Test public void display_plugin_name_when_failing_to_add_extension() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.startComponents(); thrown.expect(IllegalStateException.class); - thrown.expectMessage("Unable to register extension org.sonar.api.platform.ComponentContainerTest$UnstartableComponent"); + thrown.expectMessage("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent"); container.addExtension(plugin, UnstartableComponent.class); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java index f2b36a677fe..431890bc012 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java @@ -17,13 +17,16 @@ * 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; +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.Mockito.*; +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 { @@ -35,8 +38,8 @@ public class ComponentKeysTest { } @Test - public void generate_key_of_object() { - assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.api.platform.ComponentKeysTest.FakeComponent-fake"); + public void generate_key_of_object() throws Exception { + assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.core.platform.ComponentKeysTest.FakeComponent-fake"); } @Test diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java index d5b74341b8b..67b488584a9 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java @@ -17,7 +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. */ -package org.sonar.api.platform; +package org.sonar.core.platform; import org.junit.Test; import org.picocontainer.Characteristics; @@ -29,8 +29,8 @@ 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)); 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<PluginInfo> plugins = Arrays.asList(java1, java2, cobol); + Collections.shuffle(plugins); + + List<PluginInfo> ordered = natural().sortedCopy(plugins); + assertThat(ordered.get(0)).isSameAs(cobol); + assertThat(ordered.get(1)).isSameAs(java1); + assertThat(ordered.get(2)).isSameAs(java2); + } + + @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<String, Plugin> 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<String, PluginLoader.ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + + assertThat(defs).containsOnlyKeys("foo"); + PluginLoader.ClassloaderDef def = defs.get("foo"); + assertThat(def.basePluginKey).isEqualTo("foo"); + assertThat(def.selfFirstStrategy).isFalse(); + assertThat(def.files).containsOnly(jarFile); + assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin")); + // 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<String, PluginLoader.ClassloaderDef> 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.<File>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<DefaultPluginMetadata> 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<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>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<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>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.<PluginMetadata>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/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar Binary files differdeleted file mode 100644 index 343ad65f133..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 505311c008b..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar +++ /dev/null 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 @@ -<fake/>
\ 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 Binary files differdeleted file mode 100644 index f4b8b79b776..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 4ae5393cee5..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 6a74b55d02c..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar +++ /dev/null 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 Binary files differdeleted file mode 100644 index 8044dff8988..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar +++ /dev/null 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 <code>org.sonar.api.Extension</code> interface to browse * available extension points. - * <p/> * <p>The manifest property <code>Plugin-Class</code> must declare the name of the implementation class. * It is automatically set by sonar-packaging-maven-plugin when building plugins.</p> + * <p>Implementation must declare a public constructor with no-parameters.</p> * * @see org.sonar.api.Extension * @since 1.10 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<File> 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 <code>null</code> 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<String> getRequiredPlugins(); - - boolean isCore(); - - String getImplementationBuild(); -} |