Browse Source

SONAR-6517 apply feedback

tags/5.2-RC1
Simon Brandhof 9 years ago
parent
commit
1b0e00bd13
34 changed files with 705 additions and 644 deletions
  1. 8
    2
      server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
  2. 1
    1
      server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
  3. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
  4. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
  5. 2
    1
      server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
  6. 8
    8
      server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java
  7. 75
    51
      server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
  8. 15
    8
      server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
  9. 3
    1
      server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
  10. 4
    2
      server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
  11. 8
    8
      server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java
  12. 29
    2
      server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
  13. 12
    10
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
  14. 46
    45
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
  15. 43
    35
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
  16. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
  17. 8
    8
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java
  18. 3
    3
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java
  19. 3
    2
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java
  20. 14
    6
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java
  21. 1
    1
      sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java
  22. 10
    10
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java
  23. 9
    4
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java
  24. 57
    234
      sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java
  25. 97
    0
      sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java
  26. 2
    16
      sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
  27. 28
    8
      sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java
  28. 85
    46
      sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
  29. 36
    43
      sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
  30. 3
    3
      sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
  31. 17
    17
      sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java
  32. 15
    23
      sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
  33. 58
    41
      sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
  34. 0
    1
      sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java

+ 8
- 2
server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java View File

@@ -19,9 +19,15 @@
*/
package org.sonar.server.computation;

import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.issue.db.UpdateConflictResolver;
import org.sonar.server.computation.issue.*;
import org.sonar.core.platform.ComponentContainer;
import org.sonar.server.computation.issue.IssueCache;
import org.sonar.server.computation.issue.IssueComputation;
import org.sonar.server.computation.issue.RuleCache;
import org.sonar.server.computation.issue.RuleCacheLoader;
import org.sonar.server.computation.issue.ScmAccountCache;
import org.sonar.server.computation.issue.ScmAccountCacheLoader;
import org.sonar.server.computation.issue.SourceLinesCache;
import org.sonar.server.computation.measure.MetricCache;
import org.sonar.server.computation.step.ComputationSteps;
import org.sonar.server.platform.Platform;

+ 1
- 1
server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java View File

@@ -46,7 +46,7 @@ public class DatabaseMigrator implements ServerComponent, Startable {
private final ServerUpgradeStatus serverUpgradeStatus;

/**
* ServerPluginInstaller is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
* ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
*/
public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
ServerPluginRepository unused) {

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java View File

@@ -19,14 +19,15 @@
*/
package org.sonar.server.platform;

import org.sonar.core.platform.ComponentContainer;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.persistence.DatabaseVersion;
import org.sonar.core.platform.ComponentContainer;

import javax.annotation.CheckForNull;
import javax.servlet.ServletContext;

import java.util.Collection;
import java.util.Properties;


+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java View File

@@ -218,7 +218,7 @@ import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.ServerExtensionInstaller;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.ServerPluginUnzipper;
import org.sonar.server.plugins.ServerPluginExploder;
import org.sonar.server.plugins.UpdateCenterClient;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.ws.AvailablePluginsWsAction;
@@ -520,7 +520,7 @@ class ServerComponents {

// plugins
ServerPluginRepository.class,
ServerPluginUnzipper.class,
ServerPluginExploder.class,
PluginLoader.class,
InstalledPluginReferentialFactory.class,
ServerExtensionInstaller.class,

+ 2
- 1
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java View File

@@ -47,6 +47,7 @@ import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.apache.commons.io.FileUtils.forceMkdir;
import static org.apache.commons.io.FileUtils.toFile;
import static org.apache.commons.lang.StringUtils.substringAfterLast;
import static org.sonar.core.platform.PluginInfo.jarToPluginInfo;

/**
* Downloads plugins from update center. Files are copied in the directory extensions/downloads and then
@@ -115,7 +116,7 @@ public class PluginDownloader implements Startable {
* @return the list of download plugins as {@link PluginInfo} instances
*/
public Collection<PluginInfo> getDownloadedPlugins() {
return newArrayList(transform(listPlugins(this.downloadDir), PluginInfo.JarToPluginInfo.INSTANCE));
return newArrayList(transform(listPlugins(this.downloadDir), jarToPluginInfo()));
}

public void download(String pluginKey, Version version) {

server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java → server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginExploder.java View File

@@ -23,8 +23,8 @@ import org.apache.commons.io.FileUtils;
import org.sonar.api.ServerComponent;
import org.sonar.api.utils.ZipUtils;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginUnzipper;
import org.sonar.core.platform.UnzippedPlugin;
import org.sonar.core.platform.PluginExploder;
import org.sonar.core.platform.ExplodedPlugin;
import org.sonar.server.platform.DefaultServerFileSystem;

import java.io.File;
@@ -32,11 +32,11 @@ import java.io.File;
import static org.apache.commons.io.FileUtils.cleanDirectory;
import static org.apache.commons.io.FileUtils.forceMkdir;

public class ServerPluginUnzipper extends PluginUnzipper implements ServerComponent {
public class ServerPluginExploder extends PluginExploder implements ServerComponent {

private final DefaultServerFileSystem fs;

public ServerPluginUnzipper(DefaultServerFileSystem fs) {
public ServerPluginExploder(DefaultServerFileSystem fs) {
this.fs = fs;
}

@@ -46,20 +46,20 @@ public class ServerPluginUnzipper extends PluginUnzipper implements ServerCompon
* web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}.
*/
@Override
public UnzippedPlugin unzip(PluginInfo pluginInfo) {
public ExplodedPlugin explode(PluginInfo pluginInfo) {
File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey());
try {
forceMkdir(toDir);
cleanDirectory(toDir);

File jarSource = pluginInfo.getFile();
File jarSource = pluginInfo.getNonNullJarFile();
File jarTarget = new File(toDir, jarSource.getName());
FileUtils.copyFile(jarSource, jarTarget);
ZipUtils.unzip(jarSource, toDir, newLibFilter());
return UnzippedPlugin.createFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
} catch (Exception e) {
throw new IllegalStateException(String.format(
"Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
"Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
}
}
}

+ 75
- 51
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java View File

@@ -25,6 +25,7 @@ import com.google.common.base.Joiner;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Ordering;
import java.util.HashSet;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
@@ -42,7 +43,6 @@ import javax.annotation.Nonnull;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -57,6 +57,7 @@ import static org.apache.commons.io.FileUtils.copyFile;
import static org.apache.commons.io.FileUtils.deleteQuietly;
import static org.apache.commons.io.FileUtils.moveFile;
import static org.apache.commons.io.FileUtils.moveFileToDirectory;
import static org.sonar.core.platform.PluginInfo.jarToPluginInfo;

/**
* Manages installation and loading of plugins:
@@ -71,7 +72,7 @@ import static org.apache.commons.io.FileUtils.moveFileToDirectory;
public class ServerPluginRepository implements PluginRepository, Startable {

private static final Logger LOG = Loggers.get(ServerPluginRepository.class);
private static final String FILE_EXTENSION_JAR = "jar";
private static final String[] JAR_FILE_EXTENSIONS = new String[]{"jar"};
private static final Set<String> DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport");
private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();

@@ -160,13 +161,13 @@ public class ServerPluginRepository implements PluginRepository, Startable {
private void registerPluginInfo(PluginInfo info) {
if (blacklistedPluginKeys.contains(info.getKey())) {
LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled.", info.getName(), info.getKey());
deleteQuietly(info.getFile());
deleteQuietly(info.getNonNullJarFile());
return;
}
PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
if (existing != null) {
throw MessageException.of(format("Found two files for the same plugin [%s]: %s and %s",
info.getKey(), info.getFile().getName(), existing.getFile().getName()));
info.getKey(), info.getNonNullJarFile().getName(), existing.getNonNullJarFile().getName()));
}

}
@@ -190,15 +191,15 @@ public class ServerPluginRepository implements PluginRepository, Startable {
copyFile(sourceFile, destFile, true);
}
} catch (IOException e) {
LOG.error(format("Fail to move or copy plugin: %s to %s",
throw new IllegalStateException(format("Fail to move or copy plugin: %s to %s",
sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e);
}

PluginInfo info = PluginInfo.create(destFile);
PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
if (existing != null) {
if (!existing.getFile().getName().equals(destFile.getName())) {
deleteQuietly(existing.getFile());
if (!existing.getNonNullJarFile().getName().equals(destFile.getName())) {
deleteQuietly(existing.getNonNullJarFile());
}
LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion());
} else {
@@ -214,41 +215,20 @@ public class ServerPluginRepository implements PluginRepository, Startable {
}

/**
* Removes the plugins that are not compatible with current environment. In some cases
* plugin files can be deleted.
* Removes the plugins that are not compatible with current environment.
*/
private void unloadIncompatiblePlugins() {
// loop as long as the previous loop ignored some plugins. That allows to support dependencies
// on many levels, for example D extends C, which extends B, which requires A. If A is not installed,
// then B, C and D must be ignored. That's not possible to achieve this algorithm with a single
// iteration over plugins.
List<String> removedKeys = new ArrayList<>();
Set<String> removedKeys = new HashSet<>();
do {
removedKeys.clear();
for (PluginInfo plugin : pluginInfosByKeys.values()) {
if (!plugin.isCompatibleWith(server.getVersion())) {
throw MessageException.of(String.format(
"Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()));
}

if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !pluginInfosByKeys.containsKey(plugin.getBasePlugin())) {
// this plugin extends a plugin that is not installed
LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
if (!isCompatible(plugin, server, pluginInfosByKeys)) {
removedKeys.add(plugin.getKey());
}

for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
PluginInfo available = pluginInfosByKeys.get(requiredPlugin.getKey());
if (available == null) {
// this plugin requires a plugin that is not installed
LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
removedKeys.add(plugin.getKey());
} else if (requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(available.getVersion()) > 0) {
LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(),
requiredPlugin.getKey(), requiredPlugin.getMinimalVersion());
removedKeys.add(plugin.getKey());
}
}
}
for (String removedKey : removedKeys) {
pluginInfosByKeys.remove(removedKey);
@@ -256,6 +236,41 @@ public class ServerPluginRepository implements PluginRepository, Startable {
} while (!removedKeys.isEmpty());
}

@VisibleForTesting
static boolean isCompatible(PluginInfo plugin, Server server, Map<String, PluginInfo> allPluginsByKeys) {
if (Strings.isNullOrEmpty(plugin.getMainClass()) && Strings.isNullOrEmpty(plugin.getBasePlugin())) {
LOG.warn("Plugin {} [{}] is ignored because entry point class is not defined", plugin.getName(), plugin.getKey());
return false;
}

if (!plugin.isCompatibleWith(server.getVersion())) {
throw MessageException.of(format(
"Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()));
}

if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !allPluginsByKeys.containsKey(plugin.getBasePlugin())) {
// it extends a plugin that is not installed
LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
return false;
}

for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
PluginInfo available = allPluginsByKeys.get(requiredPlugin.getKey());
if (available == null) {
// it requires a plugin that is not installed
LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
return false;
}
if (requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(available.getVersion()) > 0) {
// it requires a more recent version
LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(),
requiredPlugin.getKey(), requiredPlugin.getMinimalVersion());
return false;
}
}
return true;
}

private void logInstalledPlugins() {
List<PluginInfo> orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values());
for (PluginInfo plugin : orderedPlugins) {
@@ -264,32 +279,41 @@ public class ServerPluginRepository implements PluginRepository, Startable {
}

private void loadInstances() {
pluginInstancesByKeys.clear();
pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
}

/**
* Uninstall a plugin and its dependents (the plugins that require the plugin to be uninstalled)
* Uninstall a plugin and its dependents
*/
public void uninstall(String pluginKey) {
for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
if (!otherPlugin.getKey().equals(pluginKey)) {
for (PluginInfo.RequiredPlugin requiredPlugin : otherPlugin.getRequiredPlugins()) {
if (requiredPlugin.getKey().equals(pluginKey)) {
uninstall(otherPlugin.getKey());
}
Set<String> uninstallKeys = new HashSet<>();
uninstallKeys.add(pluginKey);
appendDependentPluginKeys(pluginKey, uninstallKeys);

for (String uninstallKey : uninstallKeys) {
PluginInfo info = pluginInfosByKeys.get(uninstallKey);
if (!info.isCore()) {
try {
LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey());
// we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
File masterFile = new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName());
moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
} catch (IOException e) {
throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e);
}
}
}
}

PluginInfo info = pluginInfosByKeys.get(pluginKey);
if (!info.isCore()) {
try {
// we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
File masterFile = new File(fs.getInstalledPluginsDir(), info.getFile().getName());
moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
} catch (IOException e) {
throw new IllegalStateException("Fail to uninstall plugin [" + pluginKey + "]", e);
private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
if (!otherPlugin.getKey().equals(pluginKey)) {
for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
if (requirement.getKey().equals(pluginKey)) {
appendTo.add(otherPlugin.getKey());
appendDependentPluginKeys(otherPlugin.getKey(), appendTo);
}
}
}
}
}
@@ -302,7 +326,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
* @return the list of plugins to be uninstalled as {@link PluginInfo} instances
*/
public Collection<PluginInfo> getUninstalledPlugins() {
return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), PluginInfo.JarToPluginInfo.INSTANCE));
return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), jarToPluginInfo()));
}

public void cancelUninstalls() {
@@ -328,7 +352,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
public PluginInfo getPluginInfo(String key) {
PluginInfo info = pluginInfosByKeys.get(key);
if (info == null) {
throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
}
return info;
}
@@ -337,7 +361,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
public Plugin getPluginInstance(String key) {
Plugin plugin = pluginInstancesByKeys.get(key);
if (plugin == null) {
throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
}
return plugin;
}
@@ -372,7 +396,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {

private static Collection<File> listJarFiles(File dir) {
if (dir.exists()) {
return FileUtils.listFiles(dir, new String[] {FILE_EXTENSION_JAR}, false);
return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false);
}
return Collections.emptyList();
}

+ 15
- 8
server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java View File

@@ -49,7 +49,7 @@ public class DebtModelPluginRepositoryTest {

private static final String TEST_XML_PREFIX_PATH = "org/sonar/server/debt/DebtModelPluginRepositoryTest/";

private DebtModelPluginRepository modelFinder;
DebtModelPluginRepository underTest;

@Test
public void test_component_initialization() throws Exception {
@@ -63,13 +63,13 @@ public class DebtModelPluginRepositoryTest {
when(repository.getPluginInfos()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
FakePlugin fakePlugin = new FakePlugin();
when(repository.getPluginInstance(anyString())).thenReturn(fakePlugin);
modelFinder = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);
underTest = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);

// when
modelFinder.start();
underTest.start();

// assert
Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
Collection<String> contributingPluginList = underTest.getContributingPluginList();
assertThat(contributingPluginList.size()).isEqualTo(2);
assertThat(contributingPluginList).containsOnly("technical-debt", "csharp");
}
@@ -77,7 +77,7 @@ public class DebtModelPluginRepositoryTest {
@Test
public void contributing_plugin_list() {
initModel();
Collection<String> contributingPluginList = modelFinder.getContributingPluginList();
Collection<String> contributingPluginList = underTest.getContributingPluginList();
assertThat(contributingPluginList.size()).isEqualTo(2);
assertThat(contributingPluginList).contains("csharp", "java");
}
@@ -87,7 +87,7 @@ public class DebtModelPluginRepositoryTest {
initModel();
Reader xmlFileReader = null;
try {
xmlFileReader = modelFinder.createReaderForXMLFile("csharp");
xmlFileReader = underTest.createReaderForXMLFile("csharp");
assertNotNull(xmlFileReader);
List<String> lines = IOUtils.readLines(xmlFileReader);
assertThat(lines.size()).isEqualTo(25);
@@ -102,21 +102,28 @@ public class DebtModelPluginRepositoryTest {
@Test
public void return_xml_file_path_for_plugin() {
initModel();
assertThat(modelFinder.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
assertThat(underTest.getXMLFilePath("foo")).isEqualTo(TEST_XML_PREFIX_PATH + "foo-model.xml");
}

@Test
<<<<<<< HEAD
public void contain_default_model() {
modelFinder = new DebtModelPluginRepository(mock(PluginRepository.class));
modelFinder.start();
assertThat(modelFinder.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
=======
public void contain_default_model() throws Exception {
underTest = new DebtModelPluginRepository(mock(PluginRepository.class));
underTest.start();
assertThat(underTest.getContributingPluginKeyToClassLoader().keySet()).containsOnly("technical-debt");
>>>>>>> SONAR-6517 apply feedback
}

private void initModel() {
Map<String, ClassLoader> contributingPluginKeyToClassLoader = Maps.newHashMap();
contributingPluginKeyToClassLoader.put("csharp", newClassLoader());
contributingPluginKeyToClassLoader.put("java", newClassLoader());
modelFinder = new DebtModelPluginRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
underTest = new DebtModelPluginRepository(contributingPluginKeyToClassLoader, TEST_XML_PREFIX_PATH);
}

private ClassLoader newClassLoader() {

+ 3
- 1
server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java View File

@@ -23,6 +23,8 @@ import org.junit.Test;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginRepository;

import java.io.IOException;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -31,7 +33,7 @@ import static org.mockito.Mockito.when;
public class InstalledPluginReferentialFactoryTest {

@Test
public void should_create_plugin_referential() {
public void should_create_plugin_referential() throws IOException {
PluginInfo info = new PluginInfo("foo");
PluginRepository pluginRepository = mock(PluginRepository.class);
when(pluginRepository.getPluginInfos()).thenReturn(newArrayList(info));

+ 4
- 2
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java View File

@@ -23,13 +23,15 @@ import org.junit.Test;
import org.sonar.core.platform.PluginInfo;
import org.sonar.updatecenter.common.PluginReferential;

import java.io.IOException;

import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;

public class PluginReferentialMetadataConverterTest {

@Test
public void should_convert_info_to_plugin_referential() {
public void should_convert_info_to_plugin_referential() throws IOException {
PluginInfo info = new PluginInfo("foo");

PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));
@@ -39,7 +41,7 @@ public class PluginReferentialMetadataConverterTest {
}

@Test
public void should_not_add_core_plugin() {
public void should_not_add_core_plugin() throws IOException {
PluginInfo info = new PluginInfo("foo").setCore(true);

PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));

server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java → server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginExploderTest.java View File

@@ -23,7 +23,7 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.UnzippedPlugin;
import org.sonar.core.platform.ExplodedPlugin;
import org.sonar.server.platform.DefaultServerFileSystem;

import java.io.File;
@@ -32,13 +32,13 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;

public class ServerPluginUnzipperTest {
public class ServerPluginExploderTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();

DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class);
ServerPluginUnzipper underTest = new ServerPluginUnzipper(fs);
ServerPluginExploder underTest = new ServerPluginExploder(fs);

@Test
public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
@@ -47,16 +47,16 @@ public class ServerPluginUnzipperTest {
File jar = TestProjectUtils.jarOf("test-libs-plugin");
PluginInfo info = PluginInfo.create(jar);

UnzippedPlugin unzipped = underTest.unzip(info);
ExplodedPlugin exploded = underTest.explode(info);

// all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory
// web/deploy/{pluginKey}
File pluginDeployDir = new File(deployDir, "testlibs");

assertThat(unzipped.getKey()).isEqualTo("testlibs");
assertThat(unzipped.getMain()).isFile().exists().hasParent(pluginDeployDir);
assertThat(unzipped.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar");
for (File lib : unzipped.getLibs()) {
assertThat(exploded.getKey()).isEqualTo("testlibs");
assertThat(exploded.getMain()).isFile().exists().hasParent(pluginDeployDir);
assertThat(exploded.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar");
for (File lib : exploded.getLibs()) {
assertThat(lib).exists().isFile();
assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
}

+ 29
- 2
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java View File

@@ -19,7 +19,10 @@
*/
package org.sonar.server.plugins;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import java.util.Collections;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import org.junit.After;
import org.junit.Before;
@@ -30,6 +33,8 @@ import org.mockito.Mockito;
import org.sonar.api.platform.Server;
import org.sonar.api.platform.ServerUpgradeStatus;
import org.sonar.api.utils.MessageException;
import org.sonar.api.utils.log.LogTester;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginLoader;
import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.updatecenter.common.Version;
@@ -47,10 +52,13 @@ public class ServerPluginRepositoryTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();

@Rule
public LogTester logs = new LogTester();

Server server = mock(Server.class);
ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS);
PluginLoader pluginLoader = new PluginLoader(new ServerPluginUnzipper(fs));
PluginLoader pluginLoader = new PluginLoader(new ServerPluginExploder(fs));
ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader);

@Before
@@ -293,7 +301,7 @@ public class ServerPluginRepositoryTest {
}

@Test
public void fail_is_missing_required_plugin() throws Exception {
public void fail_to_get_missing_plugins() throws Exception {
try {
underTest.getPluginInfo("unknown");
fail();
@@ -309,6 +317,25 @@ public class ServerPluginRepositoryTest {
}
}

@Test
public void plugin_is_incompatible_if_no_entry_point_class() throws Exception {
PluginInfo plugin = new PluginInfo("foo").setName("Foo");
assertThat(ServerPluginRepository.isCompatible(plugin, server, Collections.<String, PluginInfo>emptyMap())).isFalse();
assertThat(logs.logs()).contains("Plugin Foo [foo] is ignored because entry point class is not defined");
}

/**
* Some plugins can only extend the classloader of base plugin, without declaring new extensions.
*/
@Test
public void plugin_is_compatible_if_no_entry_point_class_but_extend_other_plugin() throws Exception {
PluginInfo basePlugin = new PluginInfo("base").setMainClass("org.bar.Bar");
PluginInfo plugin = new PluginInfo("foo").setBasePlugin("base");
Map<String, PluginInfo> plugins = ImmutableMap.of("base", basePlugin, "foo", plugin);

assertThat(ServerPluginRepository.isCompatible(plugin, server, plugins)).isTrue();
}

private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
File jar = TestProjectUtils.jarOf(testPluginName);
// file is copied because it's supposed to be moved by the test

+ 12
- 10
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java View File

@@ -19,7 +19,9 @@
*/
package org.sonar.server.plugins.ws;

import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.platform.PluginInfo;
@@ -42,12 +44,14 @@ public class InstalledPluginsWsActionTest {
" \"plugins\":" + "[]" +
"}";

@Rule
public TemporaryFolder temp = new TemporaryFolder();

private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
private InstalledPluginsWsAction underTest = new InstalledPluginsWsAction(pluginRepository, new PluginWSCommons());

private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
private PluginInfo corePlugin = corePlugin("core1", "1.0");

@Test
public void action_installed_is_defined() {
@@ -75,7 +79,7 @@ public class InstalledPluginsWsActionTest {

@Test
public void core_plugin_are_not_returned() throws Exception {
when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin));
when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin("core1", "1.0")));

underTest.handle(request, response);

@@ -99,7 +103,9 @@ public class InstalledPluginsWsActionTest {
public void verify_properties_displayed_in_json_per_plugin() throws Exception {
String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
when(pluginRepository.getPluginInfos()).thenReturn(of(
new PluginInfo("plugKey").setName("plugName").setCore(false)
new PluginInfo("plugKey")
.setName("plugName")
.setCore(false)
.setDescription("desc_it")
.setVersion(Version.create("1.0"))
.setLicense("license_hey")
@@ -107,8 +113,8 @@ public class InstalledPluginsWsActionTest {
.setOrganizationUrl("org_url")
.setHomepageUrl("homepage_url")
.setIssueTrackerUrl("issueTracker_url")
.setFile(new File(getClass().getResource(jarFilename).toURI()))
.setImplementationBuild("sou_rev_sha1")
.setJarFile(new File(getClass().getResource(jarFilename).toURI()))
)
);

@@ -183,15 +189,11 @@ public class InstalledPluginsWsActionTest {
assertThat(response.outputAsString()).containsOnlyOnce("name2");
}

private static PluginInfo corePlugin(String key, String version) {
private PluginInfo corePlugin(String key, String version) {
return new PluginInfo(key).setName(key).setCore(true).setVersion(Version.create(version));
}

private static PluginInfo plugin(String key, String name, String version) {
return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create(version));
}

private static PluginInfo plugin(String key, String name) {
private PluginInfo plugin(String key, String name) {
return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create("1.0"));
}
}

+ 46
- 45
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java View File

@@ -19,7 +19,6 @@
*/
package org.sonar.server.plugins.ws;

import java.io.File;
import org.junit.Test;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
@@ -29,6 +28,8 @@ import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.ws.WsTester;
import org.sonar.updatecenter.common.Version;

import java.io.IOException;

import static com.google.common.collect.ImmutableList.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@@ -37,28 +38,13 @@ import static org.sonar.test.JsonAssert.assertJson;

public class PendingPluginsWsActionTest {

public static final PluginInfo GIT_PLUGIN_INFO = new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
.setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
.setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
.setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
.setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"))
.setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07");
private static final String DUMMY_CONTROLLER_KEY = "dummy";
public static final PluginInfo PLUGIN_2_2 = new PluginInfo("key2").setName("name2");
public static final PluginInfo PLUGIN_2_1 = new PluginInfo("key1").setName("name2");
public static final PluginInfo PLUGIN_0_0 = new PluginInfo("key0").setName("name0");

private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
private PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());

private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
PluginDownloader pluginDownloader = mock(PluginDownloader.class);
ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());
Request request = mock(Request.class);
WsTester.TestResponse response = new WsTester.TestResponse();

@Test
public void action_pending_is_defined() {
@@ -91,7 +77,7 @@ public class PendingPluginsWsActionTest {

@Test
public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception {
when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(gitPluginInfo()));

underTest.handle(request, response);

@@ -119,7 +105,7 @@ public class PendingPluginsWsActionTest {

@Test
public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception {
when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(gitPluginInfo()));

underTest.handle(request, response);

@@ -146,12 +132,11 @@ public class PendingPluginsWsActionTest {
}

@Test
public void installing_plugin_are_sorted_by_name_then_key_and_are_unique() throws Exception {
public void installing_plugins_are_sorted_by_name_then_key_and_are_unique() throws Exception {
when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(
PLUGIN_2_2,
PLUGIN_2_1,
PLUGIN_2_2,
PLUGIN_0_0
newPluginInfo(0).setName("Foo"),
newPluginInfo(3).setName("Bar"),
newPluginInfo(2).setName("Bar")
));

underTest.handle(request, response);
@@ -161,16 +146,16 @@ public class PendingPluginsWsActionTest {
" \"installing\": " +
" [" +
" {" +
" \"key\": \"key0\"," +
" \"name\": \"name0\"," +
" \"key\": \"key2\"," +
" \"name\": \"Bar\"," +
" }," +
" {" +
" \"key\": \"key1\"," +
" \"name\": \"name2\"," +
" \"key\": \"key3\"," +
" \"name\": \"Bar\"," +
" }," +
" {" +
" \"key\": \"key2\"," +
" \"name\": \"name2\"," +
" \"key\": \"key0\"," +
" \"name\": \"Foo\"," +
" }" +
" ]," +
" \"removing\": []" +
@@ -179,12 +164,11 @@ public class PendingPluginsWsActionTest {
}

@Test
public void removing_plugin_are_sorted_and_unique() throws Exception {
public void removing_plugins_are_sorted_and_unique() throws Exception {
when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(
PLUGIN_2_2,
PLUGIN_2_1,
PLUGIN_2_2,
PLUGIN_0_0
newPluginInfo(0).setName("Foo"),
newPluginInfo(3).setName("Bar"),
newPluginInfo(2).setName("Bar")
));

underTest.handle(request, response);
@@ -195,19 +179,36 @@ public class PendingPluginsWsActionTest {
" \"removing\": " +
" [" +
" {" +
" \"key\": \"key0\"," +
" \"name\": \"name0\"," +
" \"key\": \"key2\"," +
" \"name\": \"Bar\"," +
" }," +
" {" +
" \"key\": \"key1\"," +
" \"name\": \"name2\"," +
" \"key\": \"key3\"," +
" \"name\": \"Bar\"," +
" }," +
" {" +
" \"key\": \"key2\"," +
" \"name\": \"name2\"," +
" \"key\": \"key0\"," +
" \"name\": \"Foo\"," +
" }" +
" ]" +
"}"
);
}

public PluginInfo gitPluginInfo() {
return new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
.setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
.setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
.setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
.setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07");
}

public PluginInfo newPluginInfo(int id) throws IOException {
return new PluginInfo("key" + id).setName("name" + id);
}
}

+ 43
- 35
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java View File

@@ -19,7 +19,6 @@
*/
package org.sonar.server.plugins.ws;

import java.io.File;
import org.junit.Test;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.PluginInfo;
@@ -39,36 +38,14 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE;
import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UPGRADE;

public class PluginWSCommonsTest {
private static final PluginInfo GIT_PLUGIN_METADATA = new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
.setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
.setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
.setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
.setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"));
private static final Plugin PLUGIN = new Plugin("p_key")
.setName("p_name")
.setCategory("p_category")
.setDescription("p_description")
.setLicense("p_license")
.setOrganization("p_orga_name")
.setOrganizationUrl("p_orga_url")
.setTermsConditionsUrl("p_t_and_c_url");
private static final Release RELEASE = new Release(PLUGIN, version("1.0")).setDate(parseDate("2015-04-16"))
.setDownloadUrl("http://toto.com/file.jar")
.setDescription("release description")
.setChangelogUrl("http://change.org/plugin");

private WsTester.TestResponse response = new WsTester.TestResponse();
private JsonWriter jsonWriter = response.newJsonWriter();
private PluginWSCommons underTest = new PluginWSCommons();

WsTester.TestResponse response = new WsTester.TestResponse();
JsonWriter jsonWriter = response.newJsonWriter();
PluginWSCommons underTest = new PluginWSCommons();

@Test
public void verify_properties_written_by_writePluginMetadata() {
underTest.writePluginMetadata(jsonWriter, GIT_PLUGIN_METADATA);
underTest.writePluginMetadata(jsonWriter, gitPluginInfo());

jsonWriter.close();
assertJson(response.outputAsString()).setStrictArrayOrder(true).isSimilarTo("{" +
@@ -87,7 +64,7 @@ public class PluginWSCommonsTest {
@Test
public void verify_properties_written_by_writeMetadata() {
jsonWriter.beginObject();
underTest.writeMetadata(jsonWriter, GIT_PLUGIN_METADATA);
underTest.writeMetadata(jsonWriter, gitPluginInfo());
jsonWriter.endObject();

jsonWriter.close();
@@ -106,7 +83,7 @@ public class PluginWSCommonsTest {

@Test
public void verify_properties_written_by_writePluginUpdate() {
underTest.writePluginUpdate(jsonWriter, PluginUpdate.createForPluginRelease(RELEASE, version("1.0")));
underTest.writePluginUpdate(jsonWriter, PluginUpdate.createForPluginRelease(newRelease(), version("1.0")));

jsonWriter.close();
assertJson(response.outputAsString()).isSimilarTo("{" +
@@ -128,7 +105,7 @@ public class PluginWSCommonsTest {
@Test
public void verify_properties_written_by_writeMetadata_from_plugin() {
jsonWriter.beginObject();
underTest.writeMetadata(jsonWriter, PLUGIN);
underTest.writeMetadata(jsonWriter, newPlugin());
jsonWriter.endObject();

jsonWriter.close();
@@ -147,7 +124,7 @@ public class PluginWSCommonsTest {
@Test
public void writeRelease() {
jsonWriter.beginObject();
underTest.writeRelease(jsonWriter, RELEASE);
underTest.writeRelease(jsonWriter, newRelease());
jsonWriter.endObject();

jsonWriter.close();
@@ -197,11 +174,11 @@ public class PluginWSCommonsTest {
}

@Test
public void writeUpdate_renders_key_name_and_description_of_outgoing_dependencies() {
public void writeUpdate_renders_key_name_and_description_of_requirements() {
PluginUpdate pluginUpdate = new PluginUpdate();
pluginUpdate.setRelease(
new Release(PLUGIN, version("1.0")).addOutgoingDependency(RELEASE)
);
new Release(newPlugin(), version("1.0")).addOutgoingDependency(newRelease())
);

jsonWriter.beginObject();
underTest.writeUpdate(jsonWriter, pluginUpdate);
@@ -228,4 +205,35 @@ public class PluginWSCommonsTest {
private static Release release(String key) {
return new Release(new Plugin(key), version("1.0"));
}

private PluginInfo gitPluginInfo() {
return new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
.setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
.setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
.setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT");
}

private Plugin newPlugin() {
return new Plugin("p_key")
.setName("p_name")
.setCategory("p_category")
.setDescription("p_description")
.setLicense("p_license")
.setOrganization("p_orga_name")
.setOrganizationUrl("p_orga_url")
.setTermsConditionsUrl("p_t_and_c_url");
}

private Release newRelease() {
return new Release(newPlugin(), version("1.0")).setDate(parseDate("2015-04-16"))
.setDownloadUrl("http://toto.com/file.jar")
.setDescription("release description")
.setChangelogUrl("http://change.org/plugin");
}

}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java View File

@@ -70,6 +70,6 @@ public class GeneratePluginIndexTest {
}

private PluginInfo newInfo(String pluginKey) throws IOException {
return new PluginInfo(pluginKey).setFile(temp.newFile(pluginKey + ".jar"));
return new PluginInfo(pluginKey).setJarFile(temp.newFile(pluginKey + ".jar"));
}
}

sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java → sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginExploder.java View File

@@ -22,30 +22,30 @@ package org.sonar.batch.bootstrap;
import org.apache.commons.io.FileUtils;
import org.sonar.api.BatchComponent;
import org.sonar.api.utils.ZipUtils;
import org.sonar.core.platform.ExplodedPlugin;
import org.sonar.core.platform.PluginExploder;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginUnzipper;
import org.sonar.core.platform.UnzippedPlugin;
import org.sonar.home.cache.FileCache;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

public class BatchPluginUnzipper extends PluginUnzipper implements BatchComponent {
public class BatchPluginExploder extends PluginExploder implements BatchComponent {

private final FileCache fileCache;

public BatchPluginUnzipper(FileCache fileCache) {
public BatchPluginExploder(FileCache fileCache) {
this.fileCache = fileCache;
}

@Override
public UnzippedPlugin unzip(PluginInfo info) {
public ExplodedPlugin explode(PluginInfo info) {
try {
File dir = unzipFile(info.getFile());
return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), dir);
File dir = unzipFile(info.getNonNullJarFile());
return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), dir);
} catch (Exception e) {
throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getFile().getAbsolutePath()), e);
throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getNonNullJarFile().getAbsolutePath()), e);
}
}


+ 3
- 3
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java View File

@@ -45,6 +45,7 @@ import java.util.Map;
public class BatchPluginInstaller implements PluginInstaller {

private static final Logger LOG = Loggers.get(BatchPluginInstaller.class);
private static final String PLUGINS_INDEX_URL = "/deploy/plugins/index.txt";

private final ServerClient server;
private final FileCache fileCache;
@@ -105,10 +106,9 @@ public class BatchPluginInstaller implements PluginInstaller {
*/
@VisibleForTesting
List<RemotePlugin> listRemotePlugins() {
String url = "/deploy/plugins/index.txt";
try {
LOG.debug("Download index of plugins");
String indexContent = server.request(url);
String indexContent = server.request(PLUGINS_INDEX_URL);
String[] rows = StringUtils.split(indexContent, CharUtils.LF);
List<RemotePlugin> result = Lists.newArrayList();
for (String row : rows) {
@@ -117,7 +117,7 @@ public class BatchPluginInstaller implements PluginInstaller {
return result;

} catch (Exception e) {
throw new IllegalStateException("Fail to download list of plugins: " + url, e);
throw new IllegalStateException("Fail to download list of plugins: " + PLUGINS_INDEX_URL, e);
}
}
}

+ 3
- 2
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java View File

@@ -48,6 +48,7 @@ public class BatchPluginPredicate implements Predicate<String>, BatchComponent {
private static final String CORE_PLUGIN_KEY = "core";
private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker";
private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead.";
private static final Joiner COMMA_JOINER = Joiner.on(", ");

private final Set<String> whites = newHashSet(), blacks = newHashSet();
private final DefaultAnalysisMode mode;
@@ -75,10 +76,10 @@ public class BatchPluginPredicate implements Predicate<String>, BatchComponent {
}
}
if (!whites.isEmpty()) {
LOG.info("Include plugins: " + Joiner.on(", ").join(whites));
LOG.info("Include plugins: " + COMMA_JOINER.join(whites));
}
if (!blacks.isEmpty()) {
LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks));
LOG.info("Exclude plugins: " + COMMA_JOINER.join(blacks));
}
}


+ 14
- 6
sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java View File

@@ -19,6 +19,9 @@
*/
package org.sonar.batch.bootstrap;

import com.google.common.base.Preconditions;
import com.google.common.collect.Maps;
import java.util.HashMap;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
import org.sonar.core.platform.PluginInfo;
@@ -28,6 +31,9 @@ import org.sonar.core.platform.PluginRepository;
import java.util.Collection;
import java.util.Map;

/**
* Orchestrates the installation and loading of plugins
*/
public class BatchPluginRepository implements PluginRepository, Startable {

private final PluginInstaller installer;
@@ -43,8 +49,8 @@ public class BatchPluginRepository implements PluginRepository, Startable {

@Override
public void start() {
infosByKeys = installer.installRemotes();
pluginInstancesByKeys = loader.load(infosByKeys);
infosByKeys = Maps.newHashMap(installer.installRemotes());
pluginInstancesByKeys = Maps.newHashMap(loader.load(infosByKeys));

// this part is only used by tests
for (Map.Entry<String, Plugin> entry : installer.installLocals().entrySet()) {
@@ -70,14 +76,16 @@ public class BatchPluginRepository implements PluginRepository, Startable {

@Override
public PluginInfo getPluginInfo(String key) {
// TODO check null result
return infosByKeys.get(key);
PluginInfo info = infosByKeys.get(key);
Preconditions.checkState(info != null, String.format("Plugin [%s] does not exist", key));
return info;
}

@Override
public Plugin getPluginInstance(String key) {
// TODO check null result
return pluginInstancesByKeys.get(key);
Plugin instance = pluginInstancesByKeys.get(key);
Preconditions.checkState(instance != null, String.format("Plugin [%s] does not exist", key));
return instance;
}

@Override

+ 1
- 1
sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java View File

@@ -98,7 +98,7 @@ public class GlobalContainer extends ComponentContainer {
// plugins
BatchPluginRepository.class,
PluginLoader.class,
BatchPluginUnzipper.class,
BatchPluginExploder.class,
BatchPluginPredicate.class,
ExtensionInstaller.class,


sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java → sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginExploderTest.java View File

@@ -25,7 +25,7 @@ import org.junit.ClassRule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.UnzippedPlugin;
import org.sonar.core.platform.ExplodedPlugin;
import org.sonar.home.cache.FileCache;
import org.sonar.home.cache.FileCacheBuilder;

@@ -34,29 +34,29 @@ import java.io.IOException;

import static org.assertj.core.api.Assertions.assertThat;

public class BatchPluginUnzipperTest {
public class BatchPluginExploderTest {

@ClassRule
public static TemporaryFolder temp = new TemporaryFolder();

File userHome;
BatchPluginUnzipper underTest;
BatchPluginExploder underTest;

@Before
public void setUp() throws IOException {
userHome = temp.newFolder();
FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build();
underTest = new BatchPluginUnzipper(fileCache);
underTest = new BatchPluginExploder(fileCache);
}

@Test
public void copy_and_extract_libs() throws IOException {
File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache));
ExplodedPlugin exploded = underTest.explode(PluginInfo.create(fileFromCache));

assertThat(unzipped.getKey()).isEqualTo("checkstyle");
assertThat(unzipped.getMain()).isFile().exists();
assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
assertThat(exploded.getKey()).isEqualTo("checkstyle");
assertThat(exploded.getMain()).isFile().exists();
assertThat(exploded.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists();
}
@@ -64,7 +64,7 @@ public class BatchPluginUnzipperTest {
@Test
public void extract_only_libs() throws IOException {
File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar");
underTest.unzip(PluginInfo.create(fileFromCache));
underTest.explode(PluginInfo.create(fileFromCache));

assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists();
assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist();
@@ -72,7 +72,7 @@ public class BatchPluginUnzipperTest {
}

File getFileFromCache(String filename) throws IOException {
File src = FileUtils.toFile(BatchPluginUnzipperTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename));
File src = FileUtils.toFile(BatchPluginExploderTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename));
File destFile = new File(new File(userHome, "" + filename.hashCode()), filename);
FileUtils.copyFile(src, destFile);
return destFile;

+ 9
- 4
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java View File

@@ -67,7 +67,7 @@ public class BatchPluginPredicateTest {
}

@Test
public void accept_core_plugin_even_if_in_exclusions() {
public void accept_core_plugin_even_if_declared_in_exclusions() {
when(mode.isPreview()).thenReturn(true);
settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "core,findbugs");
BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode);
@@ -75,7 +75,7 @@ public class BatchPluginPredicateTest {
}

@Test
public void both_inclusions_and_exclusions() {
public void verify_both_inclusions_and_exclusions() {
when(mode.isPreview()).thenReturn(true);
settings
.setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
@@ -87,7 +87,7 @@ public class BatchPluginPredicateTest {
}

@Test
public void only_exclusions() {
public void test_exclusions_without_any_inclusions() {
when(mode.isPreview()).thenReturn(true);
settings.setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode);
@@ -96,8 +96,13 @@ public class BatchPluginPredicateTest {
assertThat(predicate.apply("cobertura")).isTrue();
}

/**
* The properties sonar.dryRun.includePlugins and sonar.dryRun.excludePlugins
* are deprecated. They are replaced by sonar.preview.includePlugins and
* sonar.preview.excludePlugins.
*/
@Test
public void deprecated_dry_run_settings() {
public void support_deprecated_dry_run_settings() {
when(mode.isPreview()).thenReturn(true);
settings
.setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit")

+ 57
- 234
sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java View File

@@ -17,237 +17,60 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
///*
// * SonarQube, open source software quality management tool.
// * Copyright (C) 2008-2014 SonarSource
// * mailto:contact AT sonarsource DOT com
// *
// * SonarQube is free software; you can redistribute it and/or
// * modify it under the terms of the GNU Lesser General Public
// * License as published by the Free Software Foundation; either
// * version 3 of the License, or (at your option) any later version.
// *
// * SonarQube is distributed in the hope that it will be useful,
// * but WITHOUT ANY WARRANTY; without even the implied warranty of
// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
// * Lesser General Public License for more details.
// *
// * You should have received a copy of the GNU Lesser General Public License
// * along with this program; if not, write to the Free Software Foundation,
// * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
// */
//package org.sonar.batch.bootstrap;
//
//import com.google.common.io.Resources;
//import org.apache.commons.io.FileUtils;
//import org.junit.After;
//import org.junit.Before;
//import org.junit.Rule;
//import org.junit.Test;
//import org.junit.rules.TemporaryFolder;
//import org.sonar.api.CoreProperties;
//import org.sonar.api.config.Settings;
//import org.sonar.core.plugins.RemotePlugin;
//import org.sonar.home.cache.FileCache;
//import org.sonar.home.cache.FileCacheBuilder;
//
//import java.io.File;
//import java.io.IOException;
//import java.util.Arrays;
//
//import static org.mockito.Mockito.mock;
//import static org.mockito.Mockito.when;
//
//public class BatchPluginRepositoryTest {
//
// @Rule
// public TemporaryFolder temp = new TemporaryFolder();
//
// private BatchPluginRepository repository;
// private DefaultAnalysisMode mode;
// private FileCache cache;
// private File userHome;
//
// @Before
// public void before() throws IOException {
// mode = mock(DefaultAnalysisMode.class);
// when(mode.isPreview()).thenReturn(false);
// userHome = temp.newFolder();
// cache = new FileCacheBuilder().setUserHome(userHome).build();
// }
//
// @After
// public void tearDown() {
// if (repository != null) {
// repository.stop();
// }
// }
//
// @Test
// public void shouldLoadPlugin() throws Exception {
// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
//
// DefaultPluginRepository installer = mock(DefaultPluginsRepository.class);
// when(installer.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
//
// repository = new BatchPluginRepository(installer, new Settings(), mode, new BatchPluginJarInstaller(cache));
//
// repository.doStart(Arrays.asList(checkstyle));
//
// assertThat(repository.getPlugin("checkstyle")).isNotNull();
// assertThat(repository.getMetadata()).hasSize(1);
// assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle");
// assertThat(repository.getMetadata("checkstyle").getDeployedFiles()).hasSize(4); // plugin + 3 dependencies
// }
//
// @Test
// public void shouldLoadPluginExtension() throws Exception {
// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
// RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false);
//
// DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class);
// when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
// when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"));
//
// repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache));
//
// repository.doStart(Arrays.asList(checkstyle, checkstyleExt));
//
// assertThat(repository.getPlugin("checkstyle")).isNotNull();
// assertThat(repository.getPlugin("checkstyleextensions")).isNotNull();
// assertThat(repository.getMetadata()).hasSize(2);
// assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle");
// assertThat(repository.getMetadata("checkstyleextensions").getVersion()).isEqualTo("0.1-SNAPSHOT");
// }
//
// @Test
// public void shouldExcludePluginAndItsExtensions() throws Exception {
// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true);
// RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false);
//
// DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class);
// when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar"));
// when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"));
//
// Settings settings = new Settings();
// settings.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle");
// repository = new BatchPluginRepository(downloader, settings, mode, new BatchPluginJarInstaller(cache));
//
// repository.doStart(Arrays.asList(checkstyle, checkstyleExt));
//
// assertThat(repository.getMetadata()).isEmpty();
// }
//
// private File fileFromCache(String filename) throws Exception {
// File file = new File(Resources.getResource("org/sonar/batch/bootstrap/BatchPluginRepositoryTest/" + filename).toURI());
// File destDir = new File(userHome, "cache/foomd5");
// FileUtils.forceMkdir(destDir);
// FileUtils.copyFileToDirectory(file, destDir);
// return new File(destDir, filename);
// }
//
// @Test
// public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() {
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode);
// assertThat(filter.accepts("pmd")).isTrue();
// assertThat(filter.accepts("buildbreaker")).isTrue();
// }
//
// @Test
// public void shouldBlackListBuildBreakerInPreviewMode() {
// when(mode.isPreview()).thenReturn(true);
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode);
// assertThat(filter.accepts("buildbreaker")).isFalse();
// }
//
// @Test
// public void whiteListShouldTakePrecedenceOverBlackList() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("pmd")).isTrue();
// }
//
// @Test
// public void corePluginShouldAlwaysBeInWhiteList() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("core")).isTrue();
// }
//
// @Test
// public void corePluginShouldNeverBeInBlackList() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("core")).isTrue();
// }
//
// @Test
// public void check_white_list_with_black_list() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("checkstyle")).isTrue();
// assertThat(filter.accepts("pmd")).isTrue();
// assertThat(filter.accepts("cobertura")).isFalse();
// }
//
// @Test
// public void check_white_list_when_plugin_is_in_both_list() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "cobertura,checkstyle,pmd,findbugs")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("checkstyle")).isTrue();
// assertThat(filter.accepts("pmd")).isTrue();
// assertThat(filter.accepts("cobertura")).isTrue();
// }
//
// @Test
// public void check_black_list_if_no_white_list() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("checkstyle")).isFalse();
// assertThat(filter.accepts("pmd")).isFalse();
// assertThat(filter.accepts("cobertura")).isTrue();
// }
//
// @Test
// public void should_concatenate_preview_filters() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "cockpit")
// .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "views")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd");
// when(mode.isPreview()).thenReturn(true);
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.whites).containsOnly("cockpit");
// assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd");
// }
//
// @Test
// public void should_concatenate_deprecated_dry_run_filters() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit")
// .setProperty(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, "views")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd");
// when(mode.isPreview()).thenReturn(true);
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.whites).containsOnly("cockpit");
// assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd");
// }
//
// @Test
// public void inclusions_and_exclusions_should_be_trimmed() {
// Settings settings = new Settings()
// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs")
// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura, pmd");
// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode);
// assertThat(filter.accepts("pmd")).isTrue();
// }
//
//}
package org.sonar.batch.bootstrap;

import com.google.common.collect.ImmutableMap;
import org.junit.Test;
import org.sonar.api.Plugin;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginLoader;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
import static org.mockito.Matchers.anyCollectionOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

public class BatchPluginRepositoryTest {

PluginInstaller installer = mock(PluginInstaller.class);
PluginLoader loader = mock(PluginLoader.class);
BatchPluginRepository underTest = new BatchPluginRepository(installer, loader);

@Test
public void install_and_load_plugins() throws Exception {
PluginInfo info = new PluginInfo("squid");
ImmutableMap<String, PluginInfo> infos = ImmutableMap.of("squid", info);
Plugin instance = mock(Plugin.class);
when(loader.load(infos)).thenReturn(ImmutableMap.of("squid", instance));
when(installer.installRemotes()).thenReturn(infos);

underTest.start();

assertThat(underTest.getPluginInfos()).containsOnly(info);
assertThat(underTest.getPluginInfo("squid")).isSameAs(info);
assertThat(underTest.getPluginInstance("squid")).isSameAs(instance);

underTest.stop();
verify(loader).unload(anyCollectionOf(Plugin.class));
}

@Test
public void fail_if_requesting_missing_plugin() throws Exception {
underTest.start();

try {
underTest.getPluginInfo("unknown");
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Plugin [unknown] does not exist");
}
try {
underTest.getPluginInstance("unknown");
fail();
} catch (IllegalStateException e) {
assertThat(e).hasMessage("Plugin [unknown] does not exist");
}
}
}

+ 97
- 0
sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java View File

@@ -0,0 +1,97 @@
/*
* SonarQube, open source software quality management tool.
* Copyright (C) 2008-2014 SonarSource
* mailto:contact AT sonarsource DOT com
*
* SonarQube is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* SonarQube is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
package org.sonar.core.platform;

import com.google.common.base.Preconditions;
import com.google.common.base.Strings;
import java.util.Collection;
import javax.annotation.Nullable;
import org.sonar.classloader.Mask;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
* Information about the classloader to be created for a set of plugins.
*/
class ClassloaderDef {

private final String basePluginKey;
private final Map<String, String> mainClassesByPluginKey = new HashMap<>();
private final List<File> files = new ArrayList<>();
private final Mask mask = new Mask();
private boolean selfFirstStrategy = false;
private ClassLoader classloader = null;

ClassloaderDef(String basePluginKey) {
Preconditions.checkNotNull(basePluginKey);
this.basePluginKey = basePluginKey;
}

String getBasePluginKey() {
return basePluginKey;
}

Map<String, String> getMainClassesByPluginKey() {
return mainClassesByPluginKey;
}

List<File> getFiles() {
return files;
}

Mask getMask() {
return mask;
}

boolean isSelfFirstStrategy() {
return selfFirstStrategy;
}

void setSelfFirstStrategy(boolean selfFirstStrategy) {
this.selfFirstStrategy = selfFirstStrategy;
}

/**
* Returns the newly created classloader. Throws an exception
* if null, for example because called before {@link #setBuiltClassloader(ClassLoader)}
*/
ClassLoader getBuiltClassloader() {
Preconditions.checkState(classloader != null);
return classloader;
}

void setBuiltClassloader(ClassLoader c) {
this.classloader = c;
}

void addFiles(Collection<File> c) {
this.files.addAll(c);
}

void addMainClass(String pluginKey, @Nullable String mainClass) {
if (!Strings.isNullOrEmpty(mainClass)) {
mainClassesByPluginKey.put(pluginKey, mainClass);
}
}
}

sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java → sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java View File

@@ -21,17 +21,14 @@ package org.sonar.core.platform;

import java.io.File;
import java.util.Collection;
import java.util.Collections;

import static org.apache.commons.io.FileUtils.listFiles;

public class UnzippedPlugin {
public class ExplodedPlugin {

private final String key;
private final File main;
private final Collection<File> libs;

public UnzippedPlugin(String key, File main, Collection<File> libs) {
public ExplodedPlugin(String key, File main, Collection<File> libs) {
this.key = key;
this.main = main;
this.libs = libs;
@@ -48,15 +45,4 @@ public class UnzippedPlugin {
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);
}
}

sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java → sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java View File

@@ -19,22 +19,42 @@
*/
package org.sonar.core.platform;

import java.io.File;
import java.util.Collection;
import java.util.Collections;
import org.sonar.api.utils.ZipUtils;

import java.util.zip.ZipEntry;

public abstract class PluginUnzipper {
import static org.apache.commons.io.FileUtils.listFiles;

public abstract class PluginExploder {

protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib";

public abstract UnzippedPlugin unzip(PluginInfo info);
public abstract ExplodedPlugin explode(PluginInfo info);

protected ZipUtils.ZipEntryFilter newLibFilter() {
return new ZipUtils.ZipEntryFilter() {
@Override
public boolean accept(ZipEntry entry) {
return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR);
}
};
return ZipLibFilter.INSTANCE;
}

protected ExplodedPlugin explodeFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) {
File libDir = new File(unzippedDir, PluginExploder.LIB_RELATIVE_PATH_IN_JAR);
Collection<File> libs;
if (libDir.isDirectory() && libDir.exists()) {
libs = listFiles(libDir, null, false);
} else {
libs = Collections.emptyList();
}
return new ExplodedPlugin(pluginKey, jarFile, libs);
}

private enum ZipLibFilter implements ZipUtils.ZipEntryFilter {
INSTANCE;

@Override
public boolean accept(ZipEntry entry) {
return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR);
}
}
}

+ 85
- 46
sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java View File

@@ -22,6 +22,8 @@ package org.sonar.core.platform;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import org.apache.commons.lang.StringUtils;
import org.sonar.updatecenter.common.PluginManifest;
import org.sonar.updatecenter.common.Version;
@@ -31,12 +33,18 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.HashSet;
import java.util.Set;
import java.util.regex.Pattern;

public class PluginInfo implements Comparable<PluginInfo> {

private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();

public static class RequiredPlugin {

private static final Pattern PARSER = Pattern.compile("\\w+:.+");

private final String key;
private final Version minimalVersion;

@@ -54,44 +62,94 @@ public class PluginInfo implements Comparable<PluginInfo> {
}

public static RequiredPlugin parse(String s) {
if (!s.matches("\\w+:.+")) {
if (!PARSER.matcher(s).matches()) {
throw new IllegalArgumentException("Manifest field does not have correct format: " + s);
}
String[] fields = StringUtils.split(s, ':');
return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());
}

@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
RequiredPlugin that = (RequiredPlugin) o;
return key.equals(that.key);
}

@Override
public int hashCode() {
return key.hashCode();
}
}

private File file;
private String key;
private String name;

@CheckForNull
private File jarFile;

@CheckForNull
private String mainClass;

@CheckForNull
private Version version;

@CheckForNull
private Version minimalSqVersion;
private String mainClass;

@CheckForNull
private String description;

@CheckForNull
private String organizationName;

@CheckForNull
private String organizationUrl;

@CheckForNull
private String license;

@CheckForNull
private String homepageUrl;

@CheckForNull
private String issueTrackerUrl;

private boolean useChildFirstClassLoader;

@CheckForNull
private String basePlugin;
private boolean core;

private boolean core = false;

@CheckForNull
private String implementationBuild;
private final List<RequiredPlugin> requiredPlugins = new ArrayList<>();

public PluginInfo() {
}
private final Set<RequiredPlugin> requiredPlugins = new HashSet<>();

/**
* For tests only
*/
public PluginInfo(String key) {
this.key = key;
this.name = key;
}

public PluginInfo setJarFile(@Nullable File f) {
this.jarFile = f;
return this;
}

public File getFile() {
return file;
@CheckForNull
public File getJarFile2() {
return jarFile;
}

public File getNonNullJarFile() {
Preconditions.checkNotNull(jarFile);
return jarFile;
}

public String getKey() {
@@ -112,6 +170,7 @@ public class PluginInfo implements Comparable<PluginInfo> {
return minimalSqVersion;
}

@CheckForNull
public String getMainClass() {
return mainClass;
}
@@ -164,37 +223,15 @@ public class PluginInfo implements Comparable<PluginInfo> {
return implementationBuild;
}

public List<RequiredPlugin> getRequiredPlugins() {
public Set<RequiredPlugin> getRequiredPlugins() {
return requiredPlugins;
}

/**
* Required
*/
public PluginInfo setFile(File file) {
this.file = file;
public PluginInfo setName(@Nullable String name) {
this.name = Objects.firstNonNull(name, this.key);
return this;
}

/**
* Required
*/
public PluginInfo setKey(String key) {
this.key = key;
return this;
}

/**
* Required
*/
public PluginInfo setName(String name) {
this.name = name;
return this;
}

/**
* Required
*/
public PluginInfo setVersion(Version version) {
this.version = version;
return this;
@@ -286,7 +323,7 @@ public class PluginInfo implements Comparable<PluginInfo> {

@Override
public String toString() {
return String.format("[%s]", Joiner.on(" / ").skipNulls().join(key, version, implementationBuild));
return String.format("[%s]", SLASH_JOINER.join(key, version, implementationBuild));
}

@Override
@@ -310,11 +347,9 @@ public class PluginInfo implements Comparable<PluginInfo> {

@VisibleForTesting
static PluginInfo create(File jarFile, PluginManifest manifest) {
PluginInfo info = new PluginInfo();
PluginInfo info = new PluginInfo(manifest.getKey());

// required fields
info.setKey(manifest.getKey());
info.setFile(jarFile);
info.setJarFile(jarFile);
info.setName(manifest.getName());
info.setMainClass(manifest.getMainClass());
info.setVersion(Version.create(manifest.getVersion()));
@@ -342,12 +377,16 @@ public class PluginInfo implements Comparable<PluginInfo> {
return info;
}

public enum JarToPluginInfo implements Function<File, PluginInfo> {
private enum JarToPluginInfo implements Function<File, PluginInfo> {
INSTANCE;

@Override
public PluginInfo apply(@Nonnull File jarFile) {
return create(jarFile);
}
};
}

public static Function<File, PluginInfo> jarToPluginInfo() {
return JarToPluginInfo.INSTANCE;
}
}

+ 36
- 43
sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java View File

@@ -19,6 +19,7 @@
*/
package org.sonar.core.platform;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Strings;
import org.apache.commons.lang.SystemUtils;
import org.sonar.api.BatchComponent;
@@ -27,24 +28,26 @@ import org.sonar.api.ServerComponent;
import org.sonar.api.utils.log.Loggers;
import org.sonar.classloader.ClassloaderBuilder;
import org.sonar.classloader.ClassloaderBuilder.LoadingOrder;
import org.sonar.classloader.Mask;

import java.io.Closeable;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Arrays.asList;
import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST;

/**
* Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
* the entry point classes as defined in manifests. It assumes that JAR files are compatible with current
* environment (minimal sonarqube version, compatibility between plugins, ...).
* environment (minimal sonarqube version, compatibility between plugins, ...):
* <ul>
* <li>server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)</li>
* <li>batch loads only the plugins deployed on server</li>
* </ul>
* <p/>
* Standard plugins have their own isolated classloader. Some others can extend a "base" plugin.
* In this case they share the same classloader then the base plugin.
@@ -55,30 +58,14 @@ public class PluginLoader implements BatchComponent, ServerComponent {

private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins/", "com/sonar/plugins/", "com/sonarsource/plugins/"};

/**
* Information about the classloader to be created for a set of plugins.
*/
static class ClassloaderDef {
final String basePluginKey;
final Map<String, String> mainClassesByPluginKey = new HashMap<>();
final List<File> files = new ArrayList<>();
final Mask mask = new Mask();
boolean selfFirstStrategy = false;
ClassLoader classloader = null;

public ClassloaderDef(String basePluginKey) {
this.basePluginKey = basePluginKey;
}
}
private final PluginExploder exploder;

private final PluginUnzipper unzipper;

public PluginLoader(PluginUnzipper unzipper) {
this.unzipper = unzipper;
public PluginLoader(PluginExploder exploder) {
this.exploder = exploder;
}

public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
Collection<ClassloaderDef> defs = defineClassloaders(infoByKeys).values();
Collection<ClassloaderDef> defs = defineClassloaders(infoByKeys);
buildClassloaders(defs);
return instantiatePluginInstances(defs);
}
@@ -87,7 +74,8 @@ public class PluginLoader implements BatchComponent, ServerComponent {
* Step 1 - define the different classloaders to be created. Number of classloaders can be
* different than number of plugins.
*/
Map<String, ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
@VisibleForTesting
Collection<ClassloaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
Map<String, ClassloaderDef> classloadersByBasePlugin = new HashMap<>();

for (PluginInfo info : infoByKeys.values()) {
@@ -97,39 +85,44 @@ public class PluginLoader implements BatchComponent, ServerComponent {
def = new ClassloaderDef(baseKey);
classloadersByBasePlugin.put(baseKey, def);
}
UnzippedPlugin unzippedPlugin = unzipper.unzip(info);
def.files.add(unzippedPlugin.getMain());
def.files.addAll(unzippedPlugin.getLibs());
def.mainClassesByPluginKey.put(info.getKey(), info.getMainClass());
ExplodedPlugin explodedPlugin = exploder.explode(info);
def.addFiles(asList(explodedPlugin.getMain()));
def.addFiles(explodedPlugin.getLibs());
def.addMainClass(info.getKey(), info.getMainClass());

for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) {
def.mask.addInclusion(defaultSharedResource + info.getKey() + "/");
def.getMask().addInclusion(defaultSharedResource + info.getKey() + "/");
}
if (Strings.isNullOrEmpty(info.getBasePlugin())) {
// The plugins that extend other plugins can only add some files to classloader.
// They can't change ordering strategy.
def.selfFirstStrategy = info.isUseChildFirstClassLoader();
def.setSelfFirstStrategy(info.isUseChildFirstClassLoader());
}
}
return classloadersByBasePlugin;
return classloadersByBasePlugin.values();
}

/**
* Step 2 - create classloaders with appropriate constituents and metadata
*/
void buildClassloaders(Collection<ClassloaderDef> defs) {
private void buildClassloaders(Collection<ClassloaderDef> defs) {
ClassloaderBuilder builder = new ClassloaderBuilder();
for (ClassloaderDef def : defs) {
builder
.newClassloader(def.basePluginKey, getClass().getClassLoader())
.setExportMask(def.basePluginKey, def.mask)
.setLoadingOrder(def.basePluginKey, def.selfFirstStrategy ? SELF_FIRST : LoadingOrder.PARENT_FIRST);
for (File file : def.files) {
builder.addURL(def.basePluginKey, fileToUrl(file));
.newClassloader(def.getBasePluginKey(), getClass().getClassLoader())
.setExportMask(def.getBasePluginKey(), def.getMask())
.setLoadingOrder(def.getBasePluginKey(), def.isSelfFirstStrategy() ? SELF_FIRST : LoadingOrder.PARENT_FIRST);
for (File file : def.getFiles()) {
builder.addURL(def.getBasePluginKey(), fileToUrl(file));
}
}
Map<String, ClassLoader> classloadersByBasePluginKey = builder.build();
for (ClassloaderDef def : defs) {
def.classloader = classloadersByBasePluginKey.get(def.basePluginKey);
ClassLoader builtClassloader = classloadersByBasePluginKey.get(def.getBasePluginKey());
if (builtClassloader == null) {
throw new IllegalStateException(String.format("Fail to create classloader for plugin [%s]", def.getBasePluginKey()));
}
def.setBuiltClassloader(builtClassloader);
}
}

@@ -139,16 +132,16 @@ public class PluginLoader implements BatchComponent, ServerComponent {
* @return the instances grouped by plugin key
* @throws IllegalStateException if at least one plugin can't be correctly loaded
*/
Map<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) {
private Map<String, Plugin> instantiatePluginInstances(Collection<ClassloaderDef> defs) {
// instantiate plugins
Map<String, Plugin> instancesByPluginKey = new HashMap<>();
for (ClassloaderDef def : defs) {
// the same classloader can be used by multiple plugins
for (Map.Entry<String, String> entry : def.mainClassesByPluginKey.entrySet()) {
for (Map.Entry<String, String> entry : def.getMainClassesByPluginKey().entrySet()) {
String pluginKey = entry.getKey();
String mainClass = entry.getValue();
try {
instancesByPluginKey.put(pluginKey, (Plugin) def.classloader.loadClass(mainClass).newInstance());
instancesByPluginKey.put(pluginKey, (Plugin) def.getBuiltClassloader().loadClass(mainClass).newInstance());
} catch (UnsupportedClassVersionError e) {
throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s",
pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e);
@@ -189,7 +182,7 @@ public class PluginLoader implements BatchComponent, ServerComponent {
return base;
}

private URL fileToUrl(File file) {
private static URL fileToUrl(File file) {
try {
return file.toURI().toURL();
} catch (MalformedURLException e) {

+ 3
- 3
sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java View File

@@ -35,9 +35,9 @@ public class RemotePlugin {
this.core = core;
}

public static RemotePlugin create(PluginInfo metadata) {
RemotePlugin result = new RemotePlugin(metadata.getKey(), metadata.isCore());
result.setFile(metadata.getFile());
public static RemotePlugin create(PluginInfo pluginInfo) {
RemotePlugin result = new RemotePlugin(pluginInfo.getKey(), pluginInfo.isCore());
result.setFile(pluginInfo.getNonNullJarFile());
return result;
}


sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java → sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java View File

@@ -29,7 +29,7 @@ import java.io.File;

import static org.assertj.core.api.Assertions.assertThat;

public class PluginUnzipperTest {
public class PluginExploderTest {

@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -38,41 +38,41 @@ public class PluginUnzipperTest {
public void unzip_plugin_with_libs() throws Exception {
final File jarFile = getFile("sonar-checkstyle-plugin-2.8.jar");
final File toDir = temp.newFolder();
PluginInfo pluginInfo = new PluginInfo().setKey("checkstyle").setFile(jarFile);
PluginInfo pluginInfo = new PluginInfo("checkstyle").setJarFile(jarFile);

PluginUnzipper unzipper = new PluginUnzipper() {
PluginExploder exploder = new PluginExploder() {
@Override
public UnzippedPlugin unzip(PluginInfo info) {
public ExplodedPlugin explode(PluginInfo info) {
try {
ZipUtils.unzip(jarFile, toDir, newLibFilter());
return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), toDir);
return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), toDir);
} catch (Exception e) {
throw new IllegalStateException(e);
}
}
};
UnzippedPlugin unzipped = unzipper.unzip(pluginInfo);
assertThat(unzipped.getKey()).isEqualTo("checkstyle");
assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
assertThat(unzipped.getMain()).isSameAs(jarFile);
ExplodedPlugin exploded = exploder.explode(pluginInfo);
assertThat(exploded.getKey()).isEqualTo("checkstyle");
assertThat(exploded.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar");
assertThat(exploded.getMain()).isSameAs(jarFile);
}

@Test
public void unzip_plugin_without_libs() throws Exception {
File jarFile = temp.newFile();
final File toDir = temp.newFolder();
PluginInfo pluginInfo = new PluginInfo().setFile(jarFile);
PluginInfo pluginInfo = new PluginInfo("foo").setJarFile(jarFile);

PluginUnzipper unzipper = new PluginUnzipper() {
PluginExploder exploder = new PluginExploder() {
@Override
public UnzippedPlugin unzip(PluginInfo info) {
return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir);
public ExplodedPlugin explode(PluginInfo info) {
return explodeFromUnzippedDir("foo", info.getNonNullJarFile(), toDir);
}
};
UnzippedPlugin unzipped = unzipper.unzip(pluginInfo);
assertThat(unzipped.getKey()).isEqualTo("foo");
assertThat(unzipped.getLibs()).isEmpty();
assertThat(unzipped.getMain()).isSameAs(jarFile);
ExplodedPlugin exploded = exploder.explode(pluginInfo);
assertThat(exploded.getKey()).isEqualTo("foo");
assertThat(exploded.getLibs()).isEmpty();
assertThat(exploded.getMain()).isSameAs(jarFile);
}

private File getFile(String filename) {

+ 15
- 23
sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java View File

@@ -23,17 +23,16 @@ import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.utils.ManifestUtils;
import org.sonar.updatecenter.common.PluginManifest;
import org.sonar.updatecenter.common.Version;

import javax.annotation.Nullable;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.jar.Manifest;

import static com.google.common.collect.Ordering.natural;
import static org.assertj.core.api.Assertions.assertThat;
@@ -59,21 +58,23 @@ public class PluginInfoTest {
}

@Test
public void test_compare() {
PluginInfo java1 = new PluginInfo("java").setName("Java").setVersion(Version.create("1.0"));
PluginInfo java2 = new PluginInfo("java").setName("Java").setVersion(Version.create("2.0"));
PluginInfo cobol = new PluginInfo("cobol").setName("Cobol").setVersion(Version.create("1.0"));
List<PluginInfo> plugins = Arrays.asList(java1, java2, cobol);
public void test_comparison() {
PluginInfo java1 = new PluginInfo("java").setVersion(Version.create("1.0"));
PluginInfo java2 = new PluginInfo("java").setVersion(Version.create("2.0"));
PluginInfo cobol = new PluginInfo("cobol").setVersion(Version.create("1.0"));
PluginInfo noVersion = new PluginInfo("noVersion");
List<PluginInfo> plugins = Arrays.asList(java1, java2, cobol, noVersion);
Collections.shuffle(plugins);

List<PluginInfo> ordered = natural().sortedCopy(plugins);
assertThat(ordered.get(0)).isSameAs(cobol);
assertThat(ordered.get(1)).isSameAs(java1);
assertThat(ordered.get(2)).isSameAs(java2);
assertThat(ordered.get(3)).isSameAs(noVersion);
}

@Test
public void test_compatibility_with_sq_version() {
public void test_compatibility_with_sq_version() throws IOException {
assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1")).isTrue();
assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1.0")).isTrue();
assertThat(withMinSqVersion("1.0").isCompatibleWith("1.0.0")).isTrue();
@@ -117,10 +118,9 @@ public class PluginInfoTest {
assertThat(pluginInfo.getKey()).isEqualTo("java");
assertThat(pluginInfo.getName()).isEqualTo("Java");
assertThat(pluginInfo.getVersion().getName()).isEqualTo("1.0");
assertThat(pluginInfo.getFile()).isSameAs(jarFile);
assertThat(pluginInfo.getJarFile2()).isSameAs(jarFile);
assertThat(pluginInfo.getMainClass()).isEqualTo("org.foo.FooPlugin");

// optional fields
assertThat(pluginInfo.isCore()).isFalse();
assertThat(pluginInfo.getBasePlugin()).isNull();
assertThat(pluginInfo.getDescription()).isNull();
assertThat(pluginInfo.getHomepageUrl()).isNull();
@@ -152,7 +152,7 @@ public class PluginInfoTest {
manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"});

File jarFile = temp.newFile();
PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest);
PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest).setCore(true);

assertThat(pluginInfo.getBasePlugin()).isEqualTo("findbugs");
assertThat(pluginInfo.getDescription()).isEqualTo("the desc");
@@ -164,6 +164,7 @@ public class PluginInfoTest {
assertThat(pluginInfo.getOrganizationUrl()).isEqualTo("http://sonarsource.com");
assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo("4.5.1");
assertThat(pluginInfo.getRequiredPlugins()).extracting("key").containsExactly("java", "pmd");
assertThat(pluginInfo.isCore()).isTrue();
}

@Test
@@ -177,23 +178,14 @@ public class PluginInfoTest {

@Test
public void test_toString() throws Exception {
PluginInfo pluginInfo = new PluginInfo().setKey("java").setVersion(Version.create("1.1"));
PluginInfo pluginInfo = new PluginInfo("java").setVersion(Version.create("1.1"));
assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1]");

pluginInfo.setImplementationBuild("SHA1");
assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1 / SHA1]");
}

@Test
public void isCore() throws Exception {
PluginInfo pluginInfo = new PluginInfo();
assertThat(pluginInfo.isCore()).isFalse();

pluginInfo.setCore(true);
assertThat(pluginInfo.isCore()).isTrue();
}

static PluginInfo withMinSqVersion(@Nullable String version) {
PluginInfo withMinSqVersion(@Nullable String version) throws IOException {
PluginInfo pluginInfo = new PluginInfo("foo");
if (version != null) {
pluginInfo.setMinimalSqVersion(Version.create(version));

+ 58
- 41
sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java View File

@@ -30,6 +30,7 @@ import org.sonar.api.utils.ZipUtils;

import java.io.File;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;

@@ -42,11 +43,11 @@ public class PluginLoaderTest {
public TemporaryFolder temp = new TemporaryFolder();

@Test
public void complete_test() throws Exception {
public void load_and_unload_plugins() throws Exception {
File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar"));
PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar);

PluginLoader loader = new PluginLoader(new TempPluginUnzipper());
PluginLoader loader = new PluginLoader(new TempPluginExploder());
Map<String, Plugin> instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo));

assertThat(instances).containsOnlyKeys("checkstyle");
@@ -54,74 +55,90 @@ public class PluginLoaderTest {
assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin");

loader.unload(instances.values());
// should test that classloaders are closed
// TODO test that classloaders are closed. Two strategies:
//
}

@Test
public void define_plugin_classloader__nominal() throws Exception {
public void define_classloader() throws Exception {
File jarFile = temp.newFile();
PluginInfo info = new PluginInfo("foo")
.setName("Foo")
.setJarFile(jarFile)
.setMainClass("org.foo.FooPlugin");
File jarFile = temp.newFile();
info.setFile(jarFile);

PluginLoader loader = new PluginLoader(new BasicPluginUnzipper());
Map<String, PluginLoader.ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info));
PluginLoader loader = new PluginLoader(new FakePluginExploder());
Collection<ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", info));

assertThat(defs).containsOnlyKeys("foo");
PluginLoader.ClassloaderDef def = defs.get("foo");
assertThat(def.basePluginKey).isEqualTo("foo");
assertThat(def.selfFirstStrategy).isFalse();
assertThat(def.files).containsOnly(jarFile);
assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
assertThat(defs).hasSize(1);
ClassloaderDef def = defs.iterator().next();
assertThat(def.getBasePluginKey()).isEqualTo("foo");
assertThat(def.isSelfFirstStrategy()).isFalse();
assertThat(def.getFiles()).containsOnly(jarFile);
assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
// TODO test mask - require change in sonar-classloader
}

/**
* A plugin can be extended by other plugins. In this case they share the same classloader.
* The first plugin is named "base plugin".
*/
@Test
public void define_plugin_classloader__extend_base_plugin() throws Exception {
File baseJarFile = temp.newFile(), extensionJarFile = temp.newFile();
public void define_same_classloader_for_multiple_plugins() throws Exception {
File baseJarFile = temp.newFile(), extensionJar1 = temp.newFile(), extensionJar2 = temp.newFile();
PluginInfo base = new PluginInfo("foo")
.setName("Foo")
.setJarFile(baseJarFile)
.setMainClass("org.foo.FooPlugin")
.setFile(baseJarFile);
PluginInfo extension = new PluginInfo("fooContrib")
.setName("Foo Contrib")
.setMainClass("org.foo.ContribPlugin")
.setFile(extensionJarFile)
.setUseChildFirstClassLoader(false);

PluginInfo extension1 = new PluginInfo("fooExtension1")
.setJarFile(extensionJar1)
.setMainClass("org.foo.Extension1Plugin")
.setBasePlugin("foo");

// This extension tries to change the classloader-ordering strategy of base plugin
// (see setUseChildFirstClassLoader(true)).
// That is not allowed and should be ignored -> strategy is still the one
// defined on base plugin (parent-first in this example)
PluginInfo extension2 = new PluginInfo("fooExtension2")
.setJarFile(extensionJar2)
.setMainClass("org.foo.Extension2Plugin")
.setBasePlugin("foo")

// not a base plugin, can't override base metadata -> will be ignored
.setUseChildFirstClassLoader(true);

PluginLoader loader = new PluginLoader(new BasicPluginUnzipper());
Map<String, PluginLoader.ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of("foo", base, "fooContrib", extension));
PluginLoader loader = new PluginLoader(new FakePluginExploder());

Collection<ClassloaderDef> defs = loader.defineClassloaders(ImmutableMap.of(
base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2));

assertThat(defs).containsOnlyKeys("foo");
PluginLoader.ClassloaderDef def = defs.get("foo");
assertThat(def.basePluginKey).isEqualTo("foo");
assertThat(def.selfFirstStrategy).isFalse();
assertThat(def.files).containsOnly(baseJarFile, extensionJarFile);
assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"), entry("fooContrib", "org.foo.ContribPlugin"));
assertThat(defs).hasSize(1);
ClassloaderDef def = defs.iterator().next();
assertThat(def.getBasePluginKey()).isEqualTo("foo");
assertThat(def.isSelfFirstStrategy()).isFalse();
assertThat(def.getFiles()).containsOnly(baseJarFile, extensionJar1, extensionJar2);
assertThat(def.getMainClassesByPluginKey()).containsOnly(
entry("foo", "org.foo.FooPlugin"),
entry("fooExtension1", "org.foo.Extension1Plugin"),
entry("fooExtension2", "org.foo.Extension2Plugin"));
// TODO test mask - require change in sonar-classloader
}

/**
* Does not unzip jar file.
* Does not unzip jar file. It directly returns the JAR file defined on PluginInfo.
*/
private class BasicPluginUnzipper extends PluginUnzipper {
private static class FakePluginExploder extends PluginExploder {
@Override
public UnzippedPlugin unzip(PluginInfo info) {
return new UnzippedPlugin(info.getKey(), info.getFile(), Collections.<File>emptyList());
public ExplodedPlugin explode(PluginInfo info) {
return new ExplodedPlugin(info.getKey(), info.getNonNullJarFile(), Collections.<File>emptyList());
}
}

private class TempPluginUnzipper extends PluginUnzipper {
private class TempPluginExploder extends PluginExploder {
@Override
public UnzippedPlugin unzip(PluginInfo info) {
public ExplodedPlugin explode(PluginInfo info) {
try {
File tempDir = temp.newFolder();
ZipUtils.unzip(info.getFile(), tempDir, newLibFilter());
return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), tempDir);
ZipUtils.unzip(info.getNonNullJarFile(), tempDir, newLibFilter());
return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), tempDir);

} catch (IOException e) {
throw new IllegalStateException(e);

+ 0
- 1
sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java View File

@@ -118,5 +118,4 @@ public class FileCacheTest {
assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir());
assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by");
}

}

Loading…
Cancel
Save