diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-05-06 22:08:31 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-05-11 10:24:03 +0200 |
commit | 1b0e00bd132798475390145f52ab429ddb8c301c (patch) | |
tree | cc5a16f6dec34b8da1e182ee9ff6de3cd3c07ecd /sonar-core | |
parent | 53b12f3331f7985de42b1917965d3cae8aa71a15 (diff) | |
download | sonarqube-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.java | 97 | ||||
-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.java | 131 | ||||
-rw-r--r-- | sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java | 79 | ||||
-rw-r--r-- | sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java | 6 | ||||
-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.java | 38 | ||||
-rw-r--r-- | sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java | 99 |
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); |