aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2015-05-06 22:08:31 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2015-05-11 10:24:03 +0200
commit1b0e00bd132798475390145f52ab429ddb8c301c (patch)
treecc5a16f6dec34b8da1e182ee9ff6de3cd3c07ecd /sonar-core
parent53b12f3331f7985de42b1917965d3cae8aa71a15 (diff)
downloadsonarqube-1b0e00bd132798475390145f52ab429ddb8c301c.tar.gz
sonarqube-1b0e00bd132798475390145f52ab429ddb8c301c.zip
SONAR-6517 apply feedback
Diffstat (limited to 'sonar-core')
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java97
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java (renamed from sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java)18
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java (renamed from sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java)36
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java131
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java79
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java6
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java (renamed from sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java)34
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java38
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java99
9 files changed, 341 insertions, 197 deletions
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java b/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java
new file mode 100644
index 00000000000..3ebe5ccb0fb
--- /dev/null
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ClassloaderDef.java
@@ -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);
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
index 3c73b8d658b..a5a9a500f59 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
@@ -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);
- }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java
index 5ce1ca08da7..50681f13e3d 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginExploder.java
@@ -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);
+ }
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
index e5509bbc7e7..317ced0ba21 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
@@ -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;
+ }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
index 336c4904bcf..b758f12d423 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
@@ -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) {
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
index 6fecfe0ae91..559faeb5a1a 100644
--- a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
+++ b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java
@@ -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;
}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java
index cbdd9c23356..85906b1e8ce 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginExploderTest.java
@@ -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) {
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
index 2b702748c4b..198f9e0c9f1 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
@@ -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));
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
index 7cb984b1bda..c32fc2f711a 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
@@ -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);