@@ -0,0 +1,60 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.ce.container; | |||
import java.io.File; | |||
import org.apache.commons.io.FileUtils; | |||
import org.sonar.api.platform.ServerFileSystem; | |||
import org.sonar.api.utils.ZipUtils; | |||
import org.sonar.core.platform.ExplodedPlugin; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.PluginJarExploder; | |||
/** | |||
* Explodes the plugin JARs of extensions/plugins/ into a temporary directory | |||
* dedicated to compute engine. | |||
*/ | |||
public class CePluginJarExploder extends PluginJarExploder { | |||
private static final String TEMP_RELATIVE_PATH = "ce-exploded-plugins"; | |||
private final ServerFileSystem fs; | |||
public CePluginJarExploder(ServerFileSystem fs) { | |||
this.fs = fs; | |||
} | |||
@Override | |||
public ExplodedPlugin explode(PluginInfo pluginInfo) { | |||
File tempDir = new File(fs.getTempDir(), TEMP_RELATIVE_PATH); | |||
File toDir = new File(tempDir, pluginInfo.getKey()); | |||
try { | |||
org.sonar.core.util.FileUtils.cleanDirectory(toDir); | |||
File jarSource = pluginInfo.getNonNullJarFile(); | |||
File jarTarget = new File(toDir, jarSource.getName()); | |||
FileUtils.copyFile(jarSource, jarTarget); | |||
ZipUtils.unzip(jarSource, toDir, newLibFilter()); | |||
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.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e); | |||
} | |||
} | |||
} |
@@ -0,0 +1,120 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.ce.container; | |||
import com.google.common.collect.ImmutableList; | |||
import java.io.File; | |||
import java.util.Collection; | |||
import java.util.Collections; | |||
import java.util.HashMap; | |||
import java.util.Map; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import org.apache.commons.io.FileUtils; | |||
import org.picocontainer.Startable; | |||
import org.sonar.api.Plugin; | |||
import org.sonar.api.utils.log.Loggers; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.PluginLoader; | |||
import org.sonar.core.platform.PluginRepository; | |||
import org.sonar.server.platform.DefaultServerFileSystem; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static java.lang.String.format; | |||
/** | |||
* Entry point to load plugins on startup. It assumes that plugins | |||
* have been correctly installed/uninstalled/updated during web server startup | |||
*/ | |||
public class CePluginRepository implements PluginRepository, Startable { | |||
private static final String[] JAR_FILE_EXTENSIONS = new String[] {"jar"}; | |||
private static final String NOT_STARTED_YET = "not started yet"; | |||
private final DefaultServerFileSystem fs; | |||
private final PluginLoader loader; | |||
private final AtomicBoolean started = new AtomicBoolean(false); | |||
// following fields are available after startup | |||
private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>(); | |||
private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>(); | |||
public CePluginRepository(DefaultServerFileSystem fs, PluginLoader loader) { | |||
this.fs = fs; | |||
this.loader = loader; | |||
} | |||
@Override | |||
public void start() { | |||
Loggers.get(getClass()).info("Load plugins"); | |||
for (File file : listJarFiles(fs.getInstalledPluginsDir())) { | |||
PluginInfo info = PluginInfo.create(file); | |||
pluginInfosByKeys.put(info.getKey(), info); | |||
} | |||
pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys)); | |||
started.set(true); | |||
} | |||
@Override | |||
public void stop() { | |||
// close classloaders | |||
loader.unload(pluginInstancesByKeys.values()); | |||
pluginInstancesByKeys.clear(); | |||
pluginInfosByKeys.clear(); | |||
started.set(false); | |||
} | |||
@Override | |||
public Collection<PluginInfo> getPluginInfos() { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
return ImmutableList.copyOf(pluginInfosByKeys.values()); | |||
} | |||
@Override | |||
public PluginInfo getPluginInfo(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
PluginInfo info = pluginInfosByKeys.get(key); | |||
if (info == null) { | |||
throw new IllegalArgumentException(format("Plugin [%s] does not exist", key)); | |||
} | |||
return info; | |||
} | |||
@Override | |||
public Plugin getPluginInstance(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
Plugin plugin = pluginInstancesByKeys.get(key); | |||
checkArgument(plugin != null, "Plugin [%s] does not exist", key); | |||
return plugin; | |||
} | |||
@Override | |||
public boolean hasPlugin(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
return pluginInfosByKeys.containsKey(key); | |||
} | |||
private static Collection<File> listJarFiles(File dir) { | |||
if (dir.exists()) { | |||
return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false); | |||
} | |||
return Collections.emptyList(); | |||
} | |||
} |
@@ -110,8 +110,6 @@ import org.sonar.server.platform.ServerLogging; | |||
import org.sonar.server.platform.TempFolderProvider; | |||
import org.sonar.server.plugins.InstalledPluginReferentialFactory; | |||
import org.sonar.server.plugins.ServerExtensionInstaller; | |||
import org.sonar.server.plugins.ServerPluginJarExploder; | |||
import org.sonar.server.plugins.ServerPluginRepository; | |||
import org.sonar.server.properties.ProjectSettingsFactory; | |||
import org.sonar.server.qualityprofile.BuiltInProfiles; | |||
import org.sonar.server.qualityprofile.QProfileComparison; | |||
@@ -187,9 +185,9 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer { | |||
// plugins | |||
PluginClassloaderFactory.class, | |||
ServerPluginJarExploder.class, | |||
CePluginJarExploder.class, | |||
PluginLoader.class, | |||
ServerPluginRepository.class, | |||
CePluginRepository.class, | |||
InstalledPluginReferentialFactory.class, | |||
ServerExtensionInstaller.class, | |||
@@ -0,0 +1,123 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.ce.container; | |||
import java.io.File; | |||
import java.io.IOException; | |||
import java.util.List; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.sonar.api.platform.ServerFileSystem; | |||
import org.sonar.core.platform.ExplodedPlugin; | |||
import org.sonar.core.platform.PluginInfo; | |||
import static org.apache.commons.io.FileUtils.sizeOfDirectory; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
public class CePluginJarExploderTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
DumbFileSystem fs = new DumbFileSystem(temp); | |||
CePluginJarExploder underTest = new CePluginJarExploder(fs); | |||
@Test | |||
public void explode_jar_to_temp_directory() throws Exception { | |||
PluginInfo info = PluginInfo.create(plugin1Jar()); | |||
ExplodedPlugin exploded = underTest.explode(info); | |||
// all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to a dedicated temp directory | |||
File copiedJar = exploded.getMain(); | |||
assertThat(exploded.getKey()).isEqualTo("test"); | |||
assertThat(copiedJar).isFile().exists(); | |||
assertThat(copiedJar.getParentFile()).isDirectory().hasName("test"); | |||
assertThat(copiedJar.getParentFile().getParentFile()).isDirectory().hasName("ce-exploded-plugins"); | |||
} | |||
@Test | |||
public void plugins_do_not_overlap() throws Exception { | |||
PluginInfo info1 = PluginInfo.create(plugin1Jar()); | |||
PluginInfo info2 = PluginInfo.create(plugin2Jar()); | |||
ExplodedPlugin exploded1 = underTest.explode(info1); | |||
ExplodedPlugin exploded2 = underTest.explode(info2); | |||
assertThat(exploded1.getKey()).isEqualTo("test"); | |||
assertThat(exploded1.getMain()).isFile().exists().hasName("sonar-test-plugin-0.1-SNAPSHOT.jar"); | |||
assertThat(exploded2.getKey()).isEqualTo("test2"); | |||
assertThat(exploded2.getMain()).isFile().exists().hasName("sonar-test2-plugin-0.1-SNAPSHOT.jar"); | |||
} | |||
@Test | |||
public void explode_is_reentrant() throws Exception { | |||
PluginInfo info = PluginInfo.create(plugin1Jar()); | |||
ExplodedPlugin exploded1 = underTest.explode(info); | |||
long dirSize1 = sizeOfDirectory(exploded1.getMain().getParentFile()); | |||
ExplodedPlugin exploded2 = underTest.explode(info); | |||
long dirSize2 = sizeOfDirectory(exploded2.getMain().getParentFile()); | |||
assertThat(exploded2.getMain().getCanonicalPath()).isEqualTo(exploded1.getMain().getCanonicalPath()); | |||
assertThat(dirSize1).isEqualTo(dirSize2); | |||
} | |||
private File plugin1Jar() { | |||
return new File("src/test/plugins/sonar-test-plugin/target/sonar-test-plugin-0.1-SNAPSHOT.jar"); | |||
} | |||
private File plugin2Jar() { | |||
return new File("src/test/plugins/sonar-test2-plugin/target/sonar-test2-plugin-0.1-SNAPSHOT.jar"); | |||
} | |||
private class DumbFileSystem implements ServerFileSystem { | |||
private final TemporaryFolder temp; | |||
private File tempDir; | |||
public DumbFileSystem(TemporaryFolder temp) { | |||
this.temp = temp; | |||
} | |||
@Override | |||
public File getHomeDir() { | |||
throw new UnsupportedOperationException(); | |||
} | |||
@Override | |||
public File getTempDir() { | |||
if (tempDir == null) { | |||
try { | |||
this.tempDir = temp.newFolder(); | |||
} catch (IOException e) { | |||
throw new IllegalStateException(e); | |||
} | |||
} | |||
return tempDir; | |||
} | |||
@Override | |||
public List<File> getExtensions(String dirName, String... suffixes) { | |||
throw new UnsupportedOperationException(); | |||
} | |||
} | |||
} |
@@ -0,0 +1,159 @@ | |||
/* | |||
* SonarQube | |||
* Copyright (C) 2009-2016 SonarSource SA | |||
* mailto:contact AT sonarsource DOT com | |||
* | |||
* This program 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. | |||
* | |||
* This program 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.ce.container; | |||
import com.google.common.collect.Maps; | |||
import java.io.File; | |||
import java.util.Collection; | |||
import java.util.Map; | |||
import org.junit.After; | |||
import org.junit.Rule; | |||
import org.junit.Test; | |||
import org.junit.rules.ExpectedException; | |||
import org.junit.rules.TemporaryFolder; | |||
import org.mockito.Mockito; | |||
import org.sonar.api.Plugin; | |||
import org.sonar.core.platform.PluginInfo; | |||
import org.sonar.core.platform.PluginLoader; | |||
import org.sonar.server.platform.DefaultServerFileSystem; | |||
import static org.assertj.core.api.Assertions.assertThat; | |||
import static org.mockito.Mockito.mock; | |||
import static org.mockito.Mockito.when; | |||
public class CePluginRepositoryTest { | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@Rule | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS); | |||
PluginLoader pluginLoader = new DumbPluginLoader(); | |||
CePluginRepository underTest = new CePluginRepository(fs, pluginLoader); | |||
@After | |||
public void tearDown() { | |||
underTest.stop(); | |||
} | |||
@Test | |||
public void empty_plugins() throws Exception { | |||
// empty folder | |||
when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder()); | |||
underTest.start(); | |||
assertThat(underTest.getPluginInfos()).isEmpty(); | |||
assertThat(underTest.hasPlugin("foo")).isFalse(); | |||
} | |||
@Test | |||
public void load_plugins() throws Exception { | |||
String pluginKey = "test"; | |||
when(fs.getInstalledPluginsDir()).thenReturn(new File("src/test/plugins/sonar-test-plugin/target")); | |||
underTest.start(); | |||
assertThat(underTest.getPluginInfos()).extracting("key").containsOnly(pluginKey); | |||
assertThat(underTest.getPluginInfo(pluginKey).getKey()).isEqualTo(pluginKey); | |||
assertThat(underTest.getPluginInstance(pluginKey)).isNotNull(); | |||
assertThat(underTest.hasPlugin(pluginKey)).isTrue(); | |||
} | |||
@Test | |||
public void getPluginInfo_fails_if_plugin_does_not_exist() throws Exception { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Plugin [foo] does not exist"); | |||
// empty folder | |||
when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder()); | |||
underTest.start(); | |||
underTest.getPluginInfo("foo"); | |||
} | |||
@Test | |||
public void getPluginInstance_fails_if_plugin_does_not_exist() throws Exception { | |||
expectedException.expect(IllegalArgumentException.class); | |||
expectedException.expectMessage("Plugin [foo] does not exist"); | |||
// empty folder | |||
when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder()); | |||
underTest.start(); | |||
underTest.getPluginInstance("foo"); | |||
} | |||
@Test | |||
public void getPluginInstance_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInstance("foo"); | |||
} | |||
@Test | |||
public void getPluginInfo_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInfo("foo"); | |||
} | |||
@Test | |||
public void hasPlugin_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.hasPlugin("foo"); | |||
} | |||
@Test | |||
public void getPluginInfos_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInfos(); | |||
} | |||
private static class DumbPluginLoader extends PluginLoader { | |||
public DumbPluginLoader() { | |||
super(null, null); | |||
} | |||
/** | |||
* Does nothing except returning the specified list of plugins | |||
*/ | |||
@Override | |||
public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) { | |||
Map<String, Plugin> result = Maps.newHashMap(); | |||
for (String pluginKey : infoByKeys.keySet()) { | |||
result.put(pluginKey, mock(Plugin.class)); | |||
} | |||
return result; | |||
} | |||
@Override | |||
public void unload(Collection<Plugin> plugins) { | |||
} | |||
} | |||
} |
@@ -0,0 +1,7 @@ | |||
# see README.txt | |||
!*/target/ | |||
*/target/classes/ | |||
*/target/maven-archiver/ | |||
*/target/maven-status/ | |||
*/target/test-*/ | |||
@@ -0,0 +1,3 @@ | |||
This directory provides the fake plugins used by tests. These tests are rarely changed, so generated | |||
artifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules | |||
to build. |
@@ -0,0 +1,13 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>org.sonarsource.sonarqube.tests</groupId> | |||
<artifactId>parent</artifactId> | |||
<version>0.1-SNAPSHOT</version> | |||
<packaging>pom</packaging> | |||
<modules> | |||
<module>test-base-plugin</module> | |||
</modules> | |||
</project> |
@@ -0,0 +1,36 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>org.sonarsource.sonarqube.tests</groupId> | |||
<artifactId>sonar-test-plugin</artifactId> | |||
<version>0.1-SNAPSHOT</version> | |||
<packaging>sonar-plugin</packaging> | |||
<name>Test Plugin</name> | |||
<description>Simple standalone plugin. Used by other fake plugins.</description> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<version>4.5.4</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<sourceDirectory>src</sourceDirectory> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> | |||
<artifactId>sonar-packaging-maven-plugin</artifactId> | |||
<version>1.15</version> | |||
<extensions>true</extensions> | |||
<configuration> | |||
<pluginKey>test</pluginKey> | |||
<pluginClass>TestPlugin</pluginClass> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,11 @@ | |||
import org.sonar.api.SonarPlugin; | |||
import java.util.Collections; | |||
import java.util.List; | |||
public class TestPlugin extends SonarPlugin { | |||
public List getExtensions() { | |||
return Collections.emptyList(); | |||
} | |||
} |
@@ -0,0 +1,36 @@ | |||
<?xml version="1.0" encoding="UTF-8"?> | |||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | |||
<modelVersion>4.0.0</modelVersion> | |||
<groupId>org.sonarsource.sonarqube.tests</groupId> | |||
<artifactId>sonar-test2-plugin</artifactId> | |||
<version>0.1-SNAPSHOT</version> | |||
<packaging>sonar-plugin</packaging> | |||
<name>Test Plugin</name> | |||
<description>Simple standalone plugin. Used by other fake plugins.</description> | |||
<dependencies> | |||
<dependency> | |||
<groupId>org.codehaus.sonar</groupId> | |||
<artifactId>sonar-plugin-api</artifactId> | |||
<version>4.5.4</version> | |||
<scope>provided</scope> | |||
</dependency> | |||
</dependencies> | |||
<build> | |||
<sourceDirectory>src</sourceDirectory> | |||
<plugins> | |||
<plugin> | |||
<groupId>org.sonarsource.sonar-packaging-maven-plugin</groupId> | |||
<artifactId>sonar-packaging-maven-plugin</artifactId> | |||
<version>1.15</version> | |||
<extensions>true</extensions> | |||
<configuration> | |||
<pluginKey>test2</pluginKey> | |||
<pluginClass>Test2Plugin</pluginClass> | |||
</configuration> | |||
</plugin> | |||
</plugins> | |||
</build> | |||
</project> |
@@ -0,0 +1,11 @@ | |||
import org.sonar.api.SonarPlugin; | |||
import java.util.Collections; | |||
import java.util.List; | |||
public class Test2Plugin extends SonarPlugin { | |||
public List getExtensions() { | |||
return Collections.emptyList(); | |||
} | |||
} |
@@ -23,6 +23,7 @@ import com.google.common.annotations.VisibleForTesting; | |||
import com.google.common.base.Function; | |||
import com.google.common.base.Joiner; | |||
import com.google.common.base.Strings; | |||
import com.google.common.collect.ImmutableList; | |||
import com.google.common.collect.ImmutableSet; | |||
import com.google.common.collect.Ordering; | |||
import java.io.File; | |||
@@ -34,6 +35,7 @@ import java.util.HashSet; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.concurrent.atomic.AtomicBoolean; | |||
import javax.annotation.Nonnull; | |||
import org.apache.commons.io.FileUtils; | |||
import org.picocontainer.Startable; | |||
@@ -49,6 +51,8 @@ import org.sonar.core.platform.PluginRepository; | |||
import org.sonar.server.platform.DefaultServerFileSystem; | |||
import org.sonar.updatecenter.common.Version; | |||
import static com.google.common.base.Preconditions.checkArgument; | |||
import static com.google.common.base.Preconditions.checkState; | |||
import static com.google.common.collect.Iterables.transform; | |||
import static com.google.common.collect.Lists.newArrayList; | |||
import static java.lang.String.format; | |||
@@ -77,11 +81,13 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
// List of plugins that should prevent the server to finish its startup | |||
private static final Set<String> FORBIDDEN_COMPATIBLE_PLUGINS = ImmutableSet.of("sqale", "report", "views"); | |||
private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls(); | |||
private static final String NOT_STARTED_YET = "not started yet"; | |||
private final Server server; | |||
private final DefaultServerFileSystem fs; | |||
private final ServerUpgradeStatus upgradeStatus; | |||
private final PluginLoader loader; | |||
private final AtomicBoolean started = new AtomicBoolean(false); | |||
private Set<String> blacklistedPluginKeys = DEFAULT_BLACKLISTED_PLUGINS; | |||
// following fields are available after startup | |||
@@ -109,15 +115,16 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
unloadIncompatiblePlugins(); | |||
logInstalledPlugins(); | |||
loadInstances(); | |||
started.set(true); | |||
} | |||
@Override | |||
public void stop() { | |||
// close classloaders | |||
loader.unload(pluginInstancesByKeys.values()); | |||
pluginInstancesByKeys.clear(); | |||
pluginInfosByKeys.clear(); | |||
started.set(true); | |||
} | |||
/** | |||
@@ -285,6 +292,8 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
* Uninstall a plugin and its dependents | |||
*/ | |||
public void uninstall(String pluginKey) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
Set<String> uninstallKeys = new HashSet<>(); | |||
uninstallKeys.add(pluginKey); | |||
appendDependentPluginKeys(pluginKey, uninstallKeys); | |||
@@ -342,11 +351,13 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
@Override | |||
public Collection<PluginInfo> getPluginInfos() { | |||
return pluginInfosByKeys.values(); | |||
checkState(started.get(), NOT_STARTED_YET); | |||
return ImmutableList.copyOf(pluginInfosByKeys.values()); | |||
} | |||
@Override | |||
public PluginInfo getPluginInfo(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
PluginInfo info = pluginInfosByKeys.get(key); | |||
if (info == null) { | |||
throw new IllegalArgumentException(format("Plugin [%s] does not exist", key)); | |||
@@ -356,15 +367,15 @@ public class ServerPluginRepository implements PluginRepository, Startable { | |||
@Override | |||
public Plugin getPluginInstance(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
Plugin plugin = pluginInstancesByKeys.get(key); | |||
if (plugin == null) { | |||
throw new IllegalArgumentException(format("Plugin [%s] does not exist", key)); | |||
} | |||
checkArgument(plugin != null, "Plugin [%s] does not exist", key); | |||
return plugin; | |||
} | |||
@Override | |||
public boolean hasPlugin(String key) { | |||
checkState(started.get(), NOT_STARTED_YET); | |||
return pluginInfosByKeys.containsKey(key); | |||
} | |||
@@ -50,7 +50,7 @@ import static org.mockito.Mockito.when; | |||
public class ServerPluginRepositoryTest { | |||
@Rule | |||
public ExpectedException thrown = ExpectedException.none(); | |||
public ExpectedException expectedException = ExpectedException.none(); | |||
@Rule | |||
public TemporaryFolder temp = new TemporaryFolder(); | |||
@@ -291,6 +291,7 @@ public class ServerPluginRepositoryTest { | |||
@Test | |||
public void fail_to_get_missing_plugins() { | |||
underTest.start(); | |||
try { | |||
underTest.getPluginInfo("unknown"); | |||
fail(); | |||
@@ -317,8 +318,8 @@ public class ServerPluginRepositoryTest { | |||
public void fail_when_views_is_installed() throws Exception { | |||
copyTestPluginTo("fake-views-plugin", fs.getInstalledPluginsDir()); | |||
thrown.expect(MessageException.class); | |||
thrown.expectMessage("Plugin 'views' is no more compatible with this version of SonarQube"); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Plugin 'views' is no more compatible with this version of SonarQube"); | |||
underTest.start(); | |||
} | |||
@@ -326,8 +327,8 @@ public class ServerPluginRepositoryTest { | |||
public void fail_when_sqale_plugin_is_installed() throws Exception { | |||
copyTestPluginTo("fake-sqale-plugin", fs.getInstalledPluginsDir()); | |||
thrown.expect(MessageException.class); | |||
thrown.expectMessage("Plugin 'sqale' is no more compatible with this version of SonarQube"); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Plugin 'sqale' is no more compatible with this version of SonarQube"); | |||
underTest.start(); | |||
} | |||
@@ -335,8 +336,8 @@ public class ServerPluginRepositoryTest { | |||
public void fail_when_report_is_installed() throws Exception { | |||
copyTestPluginTo("fake-report-plugin", fs.getInstalledPluginsDir()); | |||
thrown.expect(MessageException.class); | |||
thrown.expectMessage("Plugin 'report' is no more compatible with this version of SonarQube"); | |||
expectedException.expect(MessageException.class); | |||
expectedException.expectMessage("Plugin 'report' is no more compatible with this version of SonarQube"); | |||
underTest.start(); | |||
} | |||
@@ -352,6 +353,38 @@ public class ServerPluginRepositoryTest { | |||
assertThat(ServerPluginRepository.isCompatible(plugin, server, plugins)).isTrue(); | |||
} | |||
@Test | |||
public void getPluginInstance_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInstance("foo"); | |||
} | |||
@Test | |||
public void getPluginInfo_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInfo("foo"); | |||
} | |||
@Test | |||
public void hasPlugin_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.hasPlugin("foo"); | |||
} | |||
@Test | |||
public void getPluginInfos_throws_ISE_if_repo_is_not_started() { | |||
expectedException.expect(IllegalStateException.class); | |||
expectedException.expectMessage("not started yet"); | |||
underTest.getPluginInfos(); | |||
} | |||
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 |