@@ -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; |
@@ -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) { |
@@ -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; | |||
@@ -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, |
@@ -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) { |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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(); | |||
} |
@@ -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() { |
@@ -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)); |
@@ -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)); |
@@ -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()); | |||
} |
@@ -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 |
@@ -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")); | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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"); | |||
} | |||
} |
@@ -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")); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
@@ -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); | |||
} | |||
} | |||
} |
@@ -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)); | |||
} | |||
} | |||
@@ -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 |
@@ -98,7 +98,7 @@ public class GlobalContainer extends ComponentContainer { | |||
// plugins | |||
BatchPluginRepository.class, | |||
PluginLoader.class, | |||
BatchPluginUnzipper.class, | |||
BatchPluginExploder.class, | |||
BatchPluginPredicate.class, | |||
ExtensionInstaller.class, | |||
@@ -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; |
@@ -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") |
@@ -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"); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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); | |||
} | |||
} |
@@ -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); | |||
} | |||
} | |||
} |
@@ -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; | |||
} | |||
} |
@@ -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) { |
@@ -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; | |||
} | |||
@@ -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) { |
@@ -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)); |
@@ -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); |
@@ -118,5 +118,4 @@ public class FileCacheTest { | |||
assertThat(cachedFile.getParentFile().getParentFile()).isEqualTo(cache.getDir()); | |||
assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by"); | |||
} | |||
} |