diff options
Diffstat (limited to 'sonar-core')
37 files changed, 2081 insertions, 1150 deletions
diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml index 41ae4b71bff..ea0fd0be8f8 100644 --- a/sonar-core/pom.xml +++ b/sonar-core/pom.xml @@ -20,14 +20,12 @@ <scope>provided</scope> </dependency> <dependency> + <groupId>org.codehaus.sonar</groupId> + <artifactId>sonar-classloader</artifactId> + </dependency> + <dependency> <groupId>${project.groupId}</groupId> <artifactId>sonar-plugin-api</artifactId> - <exclusions> - <exclusion> - <groupId>classworlds</groupId> - <artifactId>classworlds</artifactId> - </exclusion> - </exclusions> </dependency> <dependency> <groupId>org.mybatis</groupId> @@ -90,10 +88,6 @@ <artifactId>commons-dbutils</artifactId> </dependency> <dependency> - <groupId>org.codehaus.plexus</groupId> - <artifactId>plexus-classworlds</artifactId> - </dependency> - <dependency> <groupId>com.googlecode.json-simple</groupId> <artifactId>json-simple</artifactId> </dependency> diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java index 02281c51f9e..8190b0f6e3e 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java @@ -20,41 +20,49 @@ package org.sonar.core.i18n; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; +import com.google.common.base.Preconditions; import org.apache.commons.io.IOUtils; import org.picocontainer.Startable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.BatchExtension; -import org.sonar.api.ServerExtension; import org.sonar.api.i18n.I18n; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.SonarException; import org.sonar.api.utils.System2; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import javax.annotation.CheckForNull; import javax.annotation.Nullable; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.NumberFormat; -import java.util.*; - -public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Startable { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultI18n.class); - - public static final String BUNDLE_PACKAGE = "org.sonar.l10n."; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +public class DefaultI18n implements I18n, Startable { + + private static final Logger LOG = Loggers.get(DefaultI18n.class); + private static final String BUNDLE_PACKAGE = "org.sonar.l10n."; + + private final PluginRepository pluginRepository; + private final ResourceBundle.Control control; + private final System2 system2; - private PluginRepository pluginRepository; + // the following fields are available after startup private ClassLoader classloader; private Map<String, String> propertyToBundles; - private final ResourceBundle.Control control; - private final System2 system2; public DefaultI18n(PluginRepository pluginRepository) { this(pluginRepository, System2.INSTANCE); @@ -68,9 +76,7 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start this.control = new ResourceBundle.Control() { @Override public Locale getFallbackLocale(String baseName, Locale locale) { - if (baseName == null) { - throw new NullPointerException(); - } + Preconditions.checkNotNull(baseName); Locale defaultLocale = Locale.ENGLISH; return locale.equals(defaultLocale) ? null : defaultLocale; } @@ -85,16 +91,16 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @VisibleForTesting void doStart(ClassLoader classloader) { this.classloader = classloader; - propertyToBundles = Maps.newHashMap(); - Collection<PluginMetadata> metadata = pluginRepository.getMetadata(); - if (metadata.isEmpty()) { + this.propertyToBundles = new HashMap<>(); + Collection<PluginInfo> infos = pluginRepository.getPluginInfos(); + if (infos.isEmpty()) { addPlugin("core"); } else { - for (PluginMetadata plugin : pluginRepository.getMetadata()) { + for (PluginInfo plugin : infos) { addPlugin(plugin.getKey()); } } - LOG.debug(String.format("Loaded %d properties from l10n bundles", propertyToBundles.size())); + LOG.debug("Loaded {} properties from l10n bundles", propertyToBundles.size()); } private void addPlugin(String pluginKey) { @@ -113,6 +119,9 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @Override public void stop() { + if (classloader instanceof Closeable) { + IOUtils.closeQuietly((Closeable)classloader); + } classloader = null; propertyToBundles = null; } diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java index d0eb0510763..9647eb784ca 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java @@ -19,35 +19,30 @@ */ package org.sonar.core.i18n; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; import java.util.List; +/** + * Aggregation of all plugin and core classloaders, used to search for all l10n bundles + */ class I18nClassloader extends URLClassLoader { - private static List<ClassLoader> classLoadersFromPlugin(PluginRepository pluginRepository) { - List<ClassLoader> list = Lists.newArrayList(); - for (PluginMetadata metadata : pluginRepository.getMetadata()) { - Plugin plugin = pluginRepository.getPlugin(metadata.getKey()); - list.add(plugin.getClass().getClassLoader()); - } - return list; - } - - private ClassLoader[] pluginClassloaders; + private final ClassLoader[] pluginClassloaders; public I18nClassloader(PluginRepository pluginRepository) { - this(classLoadersFromPlugin(pluginRepository)); + this(allPluginClassloaders(pluginRepository)); } + @VisibleForTesting I18nClassloader(List<ClassLoader> pluginClassloaders) { super(new URL[0]); - pluginClassloaders.add(getClass().getClassLoader()); this.pluginClassloaders = pluginClassloaders.toArray(new ClassLoader[pluginClassloaders.size()]); } @@ -59,7 +54,7 @@ class I18nClassloader extends URLClassLoader { return url; } } - return null; + return getClass().getClassLoader().getResource(name); } @Override @@ -71,4 +66,15 @@ class I18nClassloader extends URLClassLoader { public String toString() { return "i18n-classloader"; } + + private static List<ClassLoader> allPluginClassloaders(PluginRepository pluginRepository) { + // accepted limitation: some plugins extend base plugins, sharing the same classloader, so + // there may be duplicated classloaders in the list. + List<ClassLoader> list = Lists.newArrayList(); + for (PluginInfo info : pluginRepository.getPluginInfos()) { + Plugin plugin = pluginRepository.getPluginInstance(info.getKey()); + list.add(plugin.getClass().getClassLoader()); + } + return list; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java index 2d51c65fac8..f4dc7f69eca 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.core.issue.workflow; diff --git a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java b/sonar-core/src/main/java/org/sonar/core/notification/package-info.java index de0f088a8f3..c53b6d4b186 100644 --- a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/notification/package-info.java @@ -17,9 +17,6 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ @ParametersAreNonnullByDefault package org.sonar.core.notification; diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java new file mode 100644 index 00000000000..868208b12a0 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java @@ -0,0 +1,250 @@ +/* + * 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.collect.Iterables; +import org.picocontainer.Characteristics; +import org.picocontainer.ComponentAdapter; +import org.picocontainer.DefaultPicoContainer; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.behaviors.OptInCaching; +import org.picocontainer.lifecycle.ReflectionLifecycleStrategy; +import org.picocontainer.monitors.NullComponentMonitor; +import org.sonar.api.BatchComponent; +import org.sonar.api.ServerComponent; +import org.sonar.api.config.PropertyDefinitions; + +import javax.annotation.Nullable; + +import java.util.Collection; +import java.util.List; + +public class ComponentContainer implements BatchComponent, ServerComponent { + + // no need for multiple children + ComponentContainer parent, child; + MutablePicoContainer pico; + PropertyDefinitions propertyDefinitions; + ComponentKeys componentKeys; + + /** + * Create root container + */ + public ComponentContainer() { + this.parent = null; + this.child = null; + this.pico = createPicoContainer(); + this.componentKeys = new ComponentKeys(); + propertyDefinitions = new PropertyDefinitions(); + addSingleton(propertyDefinitions); + addSingleton(this); + } + + /** + * Create child container + */ + protected ComponentContainer(ComponentContainer parent) { + this.parent = parent; + this.pico = parent.pico.makeChildContainer(); + this.parent.child = this; + this.propertyDefinitions = parent.propertyDefinitions; + this.componentKeys = new ComponentKeys(); + addSingleton(this); + } + + public void execute() { + boolean threw = true; + try { + startComponents(); + threw = false; + } finally { + stopComponents(threw); + } + } + + /** + * This method MUST NOT be renamed start() because the container is registered itself in picocontainer. Starting + * a component twice is not authorized. + */ + public ComponentContainer startComponents() { + try { + doBeforeStart(); + pico.start(); + doAfterStart(); + return this; + } catch (Exception e) { + throw PicoUtils.propagate(e); + } + } + + /** + * This method aims to be overridden + */ + protected void doBeforeStart() { + // nothing + } + + /** + * This method aims to be overridden + */ + protected void doAfterStart() { + // nothing + } + + /** + * This method MUST NOT be renamed stop() because the container is registered itself in picocontainer. Starting + * a component twice is not authorized. + */ + public ComponentContainer stopComponents() { + return stopComponents(false); + } + + public ComponentContainer stopComponents(boolean swallowException) { + try { + pico.stop(); + pico.dispose(); + + } catch (RuntimeException e) { + if (!swallowException) { + throw PicoUtils.propagate(e); + } + } finally { + removeChild(); + if (parent != null) { + parent.removeChild(); + } + } + return this; + } + + /** + * @since 3.5 + */ + public ComponentContainer add(Object... objects) { + for (Object object : objects) { + if (object instanceof ComponentAdapter) { + addPicoAdapter((ComponentAdapter) object); + } else if (object instanceof Iterable) { + add(Iterables.toArray((Iterable) object, Object.class)); + } else { + addSingleton(object); + } + } + return this; + } + + public ComponentContainer addSingletons(Collection components) { + for (Object component : components) { + addSingleton(component); + } + return this; + } + + public ComponentContainer addSingleton(Object component) { + return addComponent(component, true); + } + + /** + * @param singleton return always the same instance if true, else a new instance + * is returned each time the component is requested + */ + public ComponentContainer addComponent(Object component, boolean singleton) { + Object key = componentKeys.of(component); + if (component instanceof ComponentAdapter) { + pico.addAdapter((ComponentAdapter) component); + } else { + try { + pico.as(singleton ? Characteristics.CACHE : Characteristics.NO_CACHE).addComponent(key, component); + } catch (Throwable t) { + throw new IllegalStateException("Unable to register component " + getName(component), t); + } + declareExtension(null, component); + } + return this; + } + + public ComponentContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) { + Object key = componentKeys.of(extension); + try { + pico.as(Characteristics.CACHE).addComponent(key, extension); + } catch (Throwable t) { + throw new IllegalStateException("Unable to register extension " + getName(extension), t); + } + declareExtension(pluginInfo, extension); + return this; + } + + private String getName(Object extension) { + if (extension instanceof Class) { + return ((Class) extension).getName(); + } + return getName(extension.getClass()); + } + + public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) { + propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : ""); + } + + public ComponentContainer addPicoAdapter(ComponentAdapter adapter) { + pico.addAdapter(adapter); + return this; + } + + public <T> T getComponentByType(Class<T> tClass) { + return pico.getComponent(tClass); + } + + public Object getComponentByKey(Object key) { + return pico.getComponent(key); + } + + public <T> List<T> getComponentsByType(Class<T> tClass) { + return pico.getComponents(tClass); + } + + public ComponentContainer removeChild() { + if (child != null) { + pico.removeChildContainer(child.pico); + child = null; + } + return this; + } + + public ComponentContainer createChild() { + return new ComponentContainer(this); + } + + public static MutablePicoContainer createPicoContainer() { + ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close"); + return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); + } + + public ComponentContainer getParent() { + return parent; + } + + public ComponentContainer getChild() { + return child; + } + + public MutablePicoContainer getPicoContainer() { + return pico; + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java new file mode 100644 index 00000000000..be315cd9cf7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java @@ -0,0 +1,52 @@ +/* + * 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 org.sonar.api.utils.internal.Uuids; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +class ComponentKeys { + + private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(".+@[a-f0-9]+"); + private final Set<Class> objectsWithoutToString = new HashSet<>(); + + Object of(Object component) { + return of(component, Loggers.get(ComponentKeys.class)); + } + + Object of(Object component, Logger log) { + if (component instanceof Class) { + return component; + } + String key = component.toString(); + if (IDENTITY_HASH_PATTERN.matcher(key).matches()) { + if (!objectsWithoutToString.add(component.getClass())) { + log.warn(String.format("Bad component key: %s. Please implement toString() method on class %s", key, component.getClass().getName())); + } + key += Uuids.create(); + } + return new StringBuilder().append(component.getClass().getCanonicalName()).append("-").append(key).toString(); + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java new file mode 100644 index 00000000000..ee4e1ae42a5 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java @@ -0,0 +1,47 @@ +/* + * 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.Throwables; +import org.picocontainer.PicoLifecycleException; + +class PicoUtils { + + private PicoUtils() { + // only static methods + } + + static Throwable sanitize(Throwable t) { + Throwable result = t; + Throwable cause = t.getCause(); + if (t instanceof PicoLifecycleException && cause != null) { + if ("wrapper".equals(cause.getMessage()) && cause.getCause() != null) { + result = cause.getCause(); + } else { + result = cause; + } + } + return result; + } + + static RuntimeException propagate(Throwable t) { + throw Throwables.propagate(sanitize(t)); + } +} 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 new file mode 100644 index 00000000000..e5509bbc7e7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java @@ -0,0 +1,353 @@ +/* + * 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.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import org.apache.commons.lang.StringUtils; +import org.sonar.updatecenter.common.PluginManifest; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class PluginInfo implements Comparable<PluginInfo> { + + public static class RequiredPlugin { + private final String key; + private final Version minimalVersion; + + public RequiredPlugin(String key, Version minimalVersion) { + this.key = key; + this.minimalVersion = minimalVersion; + } + + public String getKey() { + return key; + } + + public Version getMinimalVersion() { + return minimalVersion; + } + + public static RequiredPlugin parse(String s) { + if (!s.matches("\\w+:.+")) { + 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()); + } + } + + private File file; + private String key; + private String name; + private Version version; + private Version minimalSqVersion; + private String mainClass; + private String description; + private String organizationName; + private String organizationUrl; + private String license; + private String homepageUrl; + private String issueTrackerUrl; + private boolean useChildFirstClassLoader; + private String basePlugin; + private boolean core; + private String implementationBuild; + private final List<RequiredPlugin> requiredPlugins = new ArrayList<>(); + + public PluginInfo() { + } + + /** + * For tests only + */ + public PluginInfo(String key) { + this.key = key; + } + + public File getFile() { + return file; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + @CheckForNull + public Version getVersion() { + return version; + } + + @CheckForNull + public Version getMinimalSqVersion() { + return minimalSqVersion; + } + + public String getMainClass() { + return mainClass; + } + + @CheckForNull + public String getDescription() { + return description; + } + + @CheckForNull + public String getOrganizationName() { + return organizationName; + } + + @CheckForNull + public String getOrganizationUrl() { + return organizationUrl; + } + + @CheckForNull + public String getLicense() { + return license; + } + + @CheckForNull + public String getHomepageUrl() { + return homepageUrl; + } + + @CheckForNull + public String getIssueTrackerUrl() { + return issueTrackerUrl; + } + + public boolean isUseChildFirstClassLoader() { + return useChildFirstClassLoader; + } + + @CheckForNull + public String getBasePlugin() { + return basePlugin; + } + + public boolean isCore() { + return core; + } + + @CheckForNull + public String getImplementationBuild() { + return implementationBuild; + } + + public List<RequiredPlugin> getRequiredPlugins() { + return requiredPlugins; + } + + /** + * Required + */ + public PluginInfo setFile(File file) { + this.file = file; + 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; + } + + public PluginInfo setMinimalSqVersion(@Nullable Version v) { + this.minimalSqVersion = v; + return this; + } + + /** + * Required + */ + public PluginInfo setMainClass(String mainClass) { + this.mainClass = mainClass; + return this; + } + + public PluginInfo setDescription(@Nullable String description) { + this.description = description; + return this; + } + + public PluginInfo setOrganizationName(@Nullable String s) { + this.organizationName = s; + return this; + } + + public PluginInfo setOrganizationUrl(@Nullable String s) { + this.organizationUrl = s; + return this; + } + + public PluginInfo setLicense(@Nullable String license) { + this.license = license; + return this; + } + + public PluginInfo setHomepageUrl(@Nullable String s) { + this.homepageUrl = s; + return this; + } + + public PluginInfo setIssueTrackerUrl(@Nullable String s) { + this.issueTrackerUrl = s; + return this; + } + + public PluginInfo setUseChildFirstClassLoader(boolean b) { + this.useChildFirstClassLoader = b; + return this; + } + + public PluginInfo setBasePlugin(@Nullable String s) { + this.basePlugin = s; + return this; + } + + public PluginInfo setCore(boolean b) { + this.core = b; + return this; + } + + public PluginInfo setImplementationBuild(@Nullable String implementationBuild) { + this.implementationBuild = implementationBuild; + return this; + } + + public PluginInfo addRequiredPlugin(RequiredPlugin p) { + this.requiredPlugins.add(p); + return this; + } + + /** + * Find out if this plugin is compatible with a given version of SonarQube. + * The version of SQ must be greater than or equal to the minimal version + * needed by the plugin. + */ + public boolean isCompatibleWith(String sqVersion) { + if (null == this.minimalSqVersion) { + // no constraint defined on the plugin + return true; + } + + Version effectiveMin = Version.create(minimalSqVersion.getName()).removeQualifier(); + Version actualVersion = Version.create(sqVersion).removeQualifier(); + return actualVersion.compareTo(effectiveMin) >= 0; + } + + @Override + public String toString() { + return String.format("[%s]", Joiner.on(" / ").skipNulls().join(key, version, implementationBuild)); + } + + @Override + public int compareTo(PluginInfo other) { + int cmp = name.compareTo(other.name); + if (cmp != 0) { + return cmp; + } + return version.compareTo(other.version); + } + + public static PluginInfo create(File jarFile) { + try { + PluginManifest manifest = new PluginManifest(jarFile); + return create(jarFile, manifest); + + } catch (Exception e) { + throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e); + } + } + + @VisibleForTesting + static PluginInfo create(File jarFile, PluginManifest manifest) { + PluginInfo info = new PluginInfo(); + + // required fields + info.setKey(manifest.getKey()); + info.setFile(jarFile); + info.setName(manifest.getName()); + info.setMainClass(manifest.getMainClass()); + info.setVersion(Version.create(manifest.getVersion())); + + // optional fields + info.setDescription(manifest.getDescription()); + info.setLicense(manifest.getLicense()); + info.setOrganizationName(manifest.getOrganization()); + info.setOrganizationUrl(manifest.getOrganizationUrl()); + String minSqVersion = manifest.getSonarVersion(); + if (minSqVersion != null) { + info.setMinimalSqVersion(Version.create(minSqVersion)); + } + info.setHomepageUrl(manifest.getHomepage()); + info.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); + info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); + info.setBasePlugin(manifest.getBasePlugin()); + info.setImplementationBuild(manifest.getImplementationBuild()); + String[] requiredPlugins = manifest.getRequirePlugins(); + if (requiredPlugins != null) { + for (String s : requiredPlugins) { + info.addRequiredPlugin(RequiredPlugin.parse(s)); + } + } + return info; + } + + public enum JarToPluginInfo implements Function<File, PluginInfo> { + INSTANCE; + + @Override + public PluginInfo apply(@Nonnull File jarFile) { + return create(jarFile); + } + }; +} 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 new file mode 100644 index 00000000000..336c4904bcf --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java @@ -0,0 +1,199 @@ +/* + * 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.Strings; +import org.apache.commons.lang.SystemUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +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 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, ...). + * <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. + * <p/> + * This class is stateless. It does not keep classloaders and {@link Plugin} in memory. + */ +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 PluginUnzipper unzipper; + + public PluginLoader(PluginUnzipper unzipper) { + this.unzipper = unzipper; + } + + public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) { + Collection<ClassloaderDef> defs = defineClassloaders(infoByKeys).values(); + buildClassloaders(defs); + return instantiatePluginInstances(defs); + } + + /** + * 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) { + Map<String, ClassloaderDef> classloadersByBasePlugin = new HashMap<>(); + + for (PluginInfo info : infoByKeys.values()) { + String baseKey = basePluginKey(info, infoByKeys); + ClassloaderDef def = classloadersByBasePlugin.get(baseKey); + if (def == null) { + 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()); + for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { + def.mask.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(); + } + } + return classloadersByBasePlugin; + } + + /** + * Step 2 - create classloaders with appropriate constituents and metadata + */ + 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)); + } + } + Map<String, ClassLoader> classloadersByBasePluginKey = builder.build(); + for (ClassloaderDef def : defs) { + def.classloader = classloadersByBasePluginKey.get(def.basePluginKey); + } + } + + /** + * Step 3 - instantiate plugin instances ({@link Plugin} + * + * @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) { + // 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()) { + String pluginKey = entry.getKey(); + String mainClass = entry.getValue(); + try { + instancesByPluginKey.put(pluginKey, (Plugin) def.classloader.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); + } catch (Exception e) { + throw new IllegalStateException(String.format( + "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e); + } + } + } + return instancesByPluginKey; + } + + public void unload(Collection<Plugin> plugins) { + for (Plugin plugin : plugins) { + ClassLoader classLoader = plugin.getClass().getClassLoader(); + if (classLoader instanceof Closeable && classLoader != getClass().getClassLoader()) { + try { + ((Closeable) classLoader).close(); + } catch (Exception e) { + Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e); + } + } + } + } + + /** + * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then + * B and C must be attached to the classloader of A. The method returns A in the three cases. + */ + static String basePluginKey(PluginInfo plugin, Map<String, PluginInfo> allPluginsPerKey) { + String base = plugin.getKey(); + String parentKey = plugin.getBasePlugin(); + while (!Strings.isNullOrEmpty(parentKey)) { + PluginInfo parentPlugin = allPluginsPerKey.get(parentKey); + base = parentPlugin.getKey(); + parentKey = parentPlugin.getBasePlugin(); + } + return base; + } + + private URL fileToUrl(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java index 04aea1fb391..1b3b170a938 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java @@ -17,33 +17,27 @@ * 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.plugins; +package org.sonar.core.platform; -import com.google.common.collect.Lists; -import org.apache.commons.lang.StringUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.api.ServerComponent; -import java.net.URL; -import java.net.URLClassLoader; import java.util.Collection; /** - * This class loader is used to load resources from a list of URLs - see SONAR-1861. + * Provides information about the plugins installed in the dependency injection container */ -public class ResourcesClassloader extends URLClassLoader { - private Collection<URL> urls; +public interface PluginRepository extends BatchComponent, ServerComponent { - public ResourcesClassloader(Collection<URL> urls, ClassLoader parent) { - super(new URL[] {}, parent); - this.urls = Lists.newArrayList(urls); - } + Collection<PluginInfo> getPluginInfos(); - @Override - public URL findResource(String name) { - for (URL url : urls) { - if (StringUtils.endsWith(url.getPath(), name)) { - return url; - } - } - return null; - } + PluginInfo getPluginInfo(String key); + + /** + * @return the instance of {@link Plugin} for the given plugin key. Never return null. + */ + Plugin getPluginInstance(String key); + + boolean hasPlugin(String key); } diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java index e2f6dadd7a0..5ce1ca08da7 100644 --- a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java @@ -17,24 +17,24 @@ * 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; -package org.sonar.core.plugins; +import org.sonar.api.utils.ZipUtils; -import org.junit.Test; +import java.util.zip.ZipEntry; -import java.net.URL; -import java.util.Arrays; -import java.util.List; +public abstract class PluginUnzipper { -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; + protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib"; -public class ResourcesClassloaderTest { + public abstract UnzippedPlugin unzip(PluginInfo info); - @Test - public void test() throws Exception { - List<URL> urls = Arrays.asList(new URL("http://localhost:9000/deploy/plugins/checkstyle/extension.xml")); - ResourcesClassloader classLoader = new ResourcesClassloader(urls, null); - assertThat(classLoader.findResource("extension.xml"), notNullValue()); + protected ZipUtils.ZipEntryFilter newLibFilter() { + return new ZipUtils.ZipEntryFilter() { + @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/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java new file mode 100644 index 00000000000..3c73b8d658b --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java @@ -0,0 +1,62 @@ +/* + * 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 java.io.File; +import java.util.Collection; +import java.util.Collections; + +import static org.apache.commons.io.FileUtils.listFiles; + +public class UnzippedPlugin { + + private final String key; + private final File main; + private final Collection<File> libs; + + public UnzippedPlugin(String key, File main, Collection<File> libs) { + this.key = key; + this.main = main; + this.libs = libs; + } + + public String getKey() { + return key; + } + + public File getMain() { + return main; + } + + 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/package-info.java b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java new file mode 100644 index 00000000000..d93af63a5ba --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java @@ -0,0 +1,27 @@ +/* + * 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. + */ + +/** + * Provides support of DI (Dependency Injection) container and management of plugins. + */ +@ParametersAreNonnullByDefault +package org.sonar.core.platform; + +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java b/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java deleted file mode 100644 index a65036b7d94..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java +++ /dev/null @@ -1,310 +0,0 @@ -/* - * 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.plugins; - -import com.google.common.collect.ImmutableList; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.updatecenter.common.Version; - -import java.io.File; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; - -public class DefaultPluginMetadata implements PluginMetadata, Comparable<PluginMetadata> { - private File file; - private List<File> deployedFiles; - private List<String> pathsToInternalDeps; - private String key; - private String version; - private String sonarVersion; - private String name; - private String mainClass; - private String description; - private String organization; - private String organizationUrl; - private String license; - private String homepage; - private String issueTrackerUrl; - private boolean useChildFirstClassLoader; - private String basePlugin; - private boolean core; - private String implementationBuild; - private List<String> requiredPlugins; - - private DefaultPluginMetadata() { - deployedFiles = newArrayList(); - pathsToInternalDeps = newArrayList(); - requiredPlugins = newArrayList(); - } - - public static DefaultPluginMetadata create(File file) { - return new DefaultPluginMetadata().setFile(file); - } - - public static DefaultPluginMetadata create(String key) { - return new DefaultPluginMetadata().setKey(key); - } - - @Override - public File getFile() { - return file; - } - - public DefaultPluginMetadata setFile(File file) { - this.file = file; - return this; - } - - @Override - public List<File> getDeployedFiles() { - return deployedFiles; - } - - public DefaultPluginMetadata addDeployedFile(File f) { - this.deployedFiles.add(f); - return this; - } - - public List<String> getPathsToInternalDeps() { - return ImmutableList.copyOf(pathsToInternalDeps); - } - - public DefaultPluginMetadata setPathsToInternalDeps(List<String> pathsToInternalDeps) { - this.pathsToInternalDeps = ImmutableList.copyOf(pathsToInternalDeps); - return this; - } - - @Override - public String getKey() { - return key; - } - - public DefaultPluginMetadata setKey(String key) { - this.key = key; - return this; - } - - @Override - public String getName() { - return name; - } - - public DefaultPluginMetadata setName(String name) { - this.name = name; - return this; - } - - @Override - public String getMainClass() { - return mainClass; - } - - public DefaultPluginMetadata setMainClass(String mainClass) { - this.mainClass = mainClass; - return this; - } - - @Override - public String getDescription() { - return description; - } - - public DefaultPluginMetadata setDescription(String description) { - this.description = description; - return this; - } - - @Override - public String getOrganization() { - return organization; - } - - public DefaultPluginMetadata setOrganization(String organization) { - this.organization = organization; - return this; - } - - @Override - public String getOrganizationUrl() { - return organizationUrl; - } - - public DefaultPluginMetadata setOrganizationUrl(String organizationUrl) { - this.organizationUrl = organizationUrl; - return this; - } - - @Override - public String getLicense() { - return license; - } - - public DefaultPluginMetadata setLicense(String license) { - this.license = license; - return this; - } - - @Override - public String getVersion() { - return version; - } - - public DefaultPluginMetadata setVersion(String version) { - this.version = version; - return this; - } - - public String getSonarVersion() { - return sonarVersion; - } - - public DefaultPluginMetadata setSonarVersion(String sonarVersion) { - this.sonarVersion = sonarVersion; - return this; - } - - @Override - public List<String> getRequiredPlugins() { - return ImmutableList.copyOf(requiredPlugins); - } - - public DefaultPluginMetadata setRequiredPlugins(List<String> requiredPlugins) { - this.requiredPlugins = ImmutableList.copyOf(requiredPlugins); - return this; - } - - /** - * Find out if this plugin is compatible with a given version of Sonar. - * The version of sonar must be greater than or equal to the minimal version - * needed by the plugin. - * - * @param sonarVersion - * @return <code>true</code> if the plugin is compatible - */ - public boolean isCompatibleWith(String sonarVersion) { - if (null == this.sonarVersion) { - // Plugins without sonar version are so old, they are compatible with a version containing this code - return true; - } - - Version minimumVersion = Version.create(this.sonarVersion).removeQualifier(); - Version actualVersion = Version.create(sonarVersion).removeQualifier(); - return actualVersion.compareTo(minimumVersion) >= 0; - } - - @Override - public String getHomepage() { - return homepage; - } - - public DefaultPluginMetadata setHomepage(String homepage) { - this.homepage = homepage; - return this; - } - - @Override - public String getIssueTrackerUrl() { - return issueTrackerUrl; - } - - public DefaultPluginMetadata setIssueTrackerUrl(String issueTrackerUrl) { - this.issueTrackerUrl = issueTrackerUrl; - return this; - } - - public DefaultPluginMetadata setUseChildFirstClassLoader(boolean use) { - this.useChildFirstClassLoader = use; - return this; - } - - @Override - public boolean isUseChildFirstClassLoader() { - return useChildFirstClassLoader; - } - - public DefaultPluginMetadata setBasePlugin(String key) { - this.basePlugin = key; - return this; - } - - @Override - public String getBasePlugin() { - return basePlugin; - } - - @Override - public boolean isCore() { - return core; - } - - public DefaultPluginMetadata setCore(boolean b) { - this.core = b; - return this; - } - - @Override - public String getImplementationBuild() { - return implementationBuild; - } - - public DefaultPluginMetadata setImplementationBuild(String implementationBuild) { - this.implementationBuild = implementationBuild; - return this; - } - - @Override - public String getParent() { - return null; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DefaultPluginMetadata that = (DefaultPluginMetadata) o; - return key == null ? that.key == null : key.equals(that.key); - } - - @Override - public int hashCode() { - return key != null ? key.hashCode() : 0; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("key", key) - .append("version", StringUtils.defaultIfEmpty(version, "-")) - .toString(); - } - - @Override - public int compareTo(PluginMetadata other) { - return name.compareTo(other.getName()); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java deleted file mode 100644 index 8a6b4b88ef1..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java +++ /dev/null @@ -1,253 +0,0 @@ -/* - * 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.plugins; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.SystemUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.net.URL; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on - * {@link ClassWorld}. - * <p/> - * <h3>IMPORTANT</h3> - * <p> - * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible - * for all other plugins even if they are located in dependent library. - * </p> - * <p/> - * <h4>Search order for {@link ClassRealm} :</h4> - * <ul> - * <li>parent class loader (passed via the constructor) if there is one</li> - * <li>imports</li> - * <li>realm's constituents</li> - * <li>parent realm</li> - * </ul> - */ -public class PluginClassloaders { - - private static final String[] PREFIXES_TO_EXPORT = {"org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins."}; - private static final Logger LOG = LoggerFactory.getLogger(PluginClassloaders.class); - - private ClassWorld world; - private ClassLoader baseClassloader; - private boolean done = false; - - public PluginClassloaders(ClassLoader baseClassloader) { - this(baseClassloader, new ClassWorld()); - } - - @VisibleForTesting - PluginClassloaders(ClassLoader baseClassloader, ClassWorld world) { - this.baseClassloader = baseClassloader; - this.world = world; - } - - public Map<String, Plugin> init(Collection<PluginMetadata> plugins) { - List<PluginMetadata> children = Lists.newArrayList(); - for (PluginMetadata plugin : plugins) { - if (StringUtils.isBlank(plugin.getBasePlugin())) { - add(plugin); - } else { - children.add(plugin); - } - } - - for (PluginMetadata child : children) { - extend(child); - } - - done(); - - Map<String, Plugin> pluginsByKey = Maps.newHashMap(); - for (PluginMetadata metadata : plugins) { - pluginsByKey.put(metadata.getKey(), instantiatePlugin(metadata)); - } - return pluginsByKey; - } - - public ClassLoader add(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - List<URL> resources = Lists.newArrayList(); - List<URL> others = Lists.newArrayList(); - for (File file : plugin.getDeployedFiles()) { - if (isResource(file)) { - resources.add(file.toURI().toURL()); - } else { - others.add(file.toURI().toURL()); - } - } - ClassLoader parent; - if (resources.isEmpty()) { - parent = baseClassloader; - } else { - parent = new ResourcesClassloader(resources, baseClassloader); - } - ClassRealm realm; - if (plugin.isUseChildFirstClassLoader()) { - ClassRealm parentRealm = world.newRealm(plugin.getKey() + "-parent", parent); - realm = parentRealm.createChildRealm(plugin.getKey()); - } else { - realm = world.newRealm(plugin.getKey(), parent); - } - for (URL url : others) { - realm.addURL(url); - } - return realm; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", plugin.getKey(), - SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to build the classloader of %s", plugin.getKey()), e); - } - } - - public boolean extend(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - ClassRealm base = world.getRealm(plugin.getBasePlugin()); - if (base == null) { - // Ignored, because base plugin is not installed - LOG.warn(String.format("Plugin %s is ignored because base plugin is not installed: %s", - plugin.getKey(), plugin.getBasePlugin())); - return false; - } - // we create new realm to be able to return it by key without conversion to baseKey - base.createChildRealm(plugin.getKey()); - for (File file : plugin.getDeployedFiles()) { - base.addURL(file.toURI().toURL()); - } - return true; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to extend the plugin %s for %s", - plugin.getBasePlugin(), plugin.getKey()), e); - } - } - - /** - * Establishes dependencies among ClassLoaders. - */ - public void done() { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - for (Object o : world.getRealms()) { - ClassRealm realm = (ClassRealm) o; - if (!StringUtils.endsWith(realm.getId(), "-parent")) { - String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; - for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { - // important to have dot at the end of package name only for classworlds 1.1 - packagesToExport[i] = String.format("%s%s.api", PREFIXES_TO_EXPORT[i], realm.getId()); - } - export(realm, packagesToExport); - } - } - done = true; - } - - /** - * Exports specified packages from given ClassRealm to all others. - */ - private void export(ClassRealm realm, String... packages) { - for (Object o : world.getRealms()) { - ClassRealm dep = (ClassRealm) o; - if (!StringUtils.equals(dep.getId(), realm.getId())) { - try { - for (String packageName : packages) { - dep.importFrom(realm.getId(), packageName); - } - } catch (NoSuchRealmException e) { - // should never happen - throw new SonarException(e); - } - } - } - } - - /** - * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. - */ - public ClassLoader get(String key) { - if (!done) { - throw new IllegalStateException("Plugin classloaders are not initialized"); - } - try { - return world.getRealm(key); - } catch (NoSuchRealmException e) { - return null; - } - } - - public Plugin instantiatePlugin(PluginMetadata plugin) { - try { - Class clazz = get(plugin.getKey()).loadClass(plugin.getMainClass()); - return (Plugin) clazz.newInstance(); - - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to load plugin %s", plugin.getKey()), e); - } - } - - private boolean isResource(File file) { - return !StringUtils.endsWithIgnoreCase(file.getName(), ".jar") && !file.isDirectory(); - } - - public void clean() { - for (ClassRealm realm : world.getRealms()) { - try { - world.disposeRealm(realm.getId()); - } catch (Exception e) { - // Ignore - } - } - world = null; - baseClassloader=null; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java deleted file mode 100644 index 7c5114a323b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java +++ /dev/null @@ -1,111 +0,0 @@ -/* - * 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.plugins; - -import com.google.common.base.Function; -import org.sonar.api.BatchComponent; -import org.sonar.api.ServerComponent; -import org.sonar.api.utils.SonarException; -import org.sonar.updatecenter.common.PluginManifest; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -public abstract class PluginJarInstaller implements BatchComponent, ServerComponent { - - protected static final String FAIL_TO_INSTALL_PLUGIN = "Fail to install plugin: "; - - protected void install(DefaultPluginMetadata metadata, @Nullable File pluginBasedir, File deployedPlugin) { - try { - metadata.addDeployedFile(deployedPlugin); - copyDependencies(metadata, deployedPlugin, pluginBasedir); - } catch (IOException e) { - throw new SonarException(FAIL_TO_INSTALL_PLUGIN + metadata, e); - } - } - - private void copyDependencies(DefaultPluginMetadata metadata, File pluginFile, @Nullable File pluginBasedir) throws IOException { - if (!metadata.getPathsToInternalDeps().isEmpty()) { - // needs to unzip the jar - File baseDir = extractPluginDependencies(pluginFile, pluginBasedir); - for (String depPath : metadata.getPathsToInternalDeps()) { - File dependency = new File(baseDir, depPath); - if (!dependency.isFile() || !dependency.exists()) { - throw new IllegalArgumentException("Dependency " + depPath + " can not be found in " + pluginFile.getName()); - } - metadata.addDeployedFile(dependency); - } - } - } - - protected abstract File extractPluginDependencies(File pluginFile, @Nullable File pluginBasedir) throws IOException; - - public DefaultPluginMetadata extractMetadata(File file, boolean isCore) { - try { - PluginManifest manifest = new PluginManifest(file); - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(file); - metadata.setKey(manifest.getKey()); - metadata.setName(manifest.getName()); - metadata.setDescription(manifest.getDescription()); - metadata.setLicense(manifest.getLicense()); - metadata.setOrganization(manifest.getOrganization()); - metadata.setOrganizationUrl(manifest.getOrganizationUrl()); - metadata.setMainClass(manifest.getMainClass()); - metadata.setVersion(manifest.getVersion()); - metadata.setSonarVersion(manifest.getSonarVersion()); - metadata.setHomepage(manifest.getHomepage()); - metadata.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); - metadata.setPathsToInternalDeps(Arrays.asList(manifest.getDependencies())); - metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); - metadata.setBasePlugin(manifest.getBasePlugin()); - metadata.setImplementationBuild(manifest.getImplementationBuild()); - metadata.setRequiredPlugins(Arrays.asList(manifest.getRequirePlugins())); - metadata.setCore(isCore); - return metadata; - - } catch (IOException e) { - throw new IllegalStateException("Fail to extract plugin metadata from file: " + file, e); - } - } - - public Function<File, DefaultPluginMetadata> fileToPlugin() { - return jarFileToPlugin; - } - - public Function<File, DefaultPluginMetadata> fileToCorePlugin() { - return jarFileToCorePlugin; - } - - private final Function<File, DefaultPluginMetadata> jarFileToCorePlugin = new Function<File, DefaultPluginMetadata>() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, true); - } - }; - private final Function<File, DefaultPluginMetadata> jarFileToPlugin = new Function<File, DefaultPluginMetadata>() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, false); - } - }; -} 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 3bc254a3ff1..6fecfe0ae91 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 @@ -20,6 +20,7 @@ package org.sonar.core.plugins; import org.apache.commons.lang.StringUtils; +import org.sonar.core.platform.PluginInfo; import org.sonar.home.cache.FileHashes; import java.io.File; @@ -34,7 +35,7 @@ public class RemotePlugin { this.core = core; } - public static RemotePlugin create(DefaultPluginMetadata metadata) { + public static RemotePlugin create(PluginInfo metadata) { RemotePlugin result = new RemotePlugin(metadata.getKey(), metadata.isCore()); result.setFile(metadata.getFile()); return result; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java index d3208e7de69..e95a48da83b 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java @@ -17,9 +17,7 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -/** - * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts. - */ + @ParametersAreNonnullByDefault package org.sonar.core.plugins; diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java index b3acbde594a..110694aff65 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java @@ -24,10 +24,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -51,8 +51,8 @@ public class DefaultI18nTest { @Before public void before() { PluginRepository pluginRepository = mock(PluginRepository.class); - List<PluginMetadata> plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); - when(pluginRepository.getMetadata()).thenReturn(plugins); + List<PluginInfo> plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); + when(pluginRepository.getPluginInfos()).thenReturn(plugins); manager = new DefaultI18n(pluginRepository, system2); manager.doStart(getClass().getClassLoader()); @@ -222,8 +222,8 @@ public class DefaultI18nTest { return new URLClassLoader(urls); } - private PluginMetadata newPlugin(String key) { - PluginMetadata plugin = mock(PluginMetadata.class); + private PluginInfo newPlugin(String key) { + PluginInfo plugin = mock(PluginInfo.class); when(plugin.getKey()).thenReturn(key); return plugin; } diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java index 971a7573965..19f9add77cc 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -80,7 +80,7 @@ public class I18nClassloaderTest { private static URLClassLoader newClassLoader(String... resourcePaths) { URL[] urls = new URL[resourcePaths.length]; for (int index = 0; index < resourcePaths.length; index++) { - urls[index] = DefaultI18nTest.class.getResource(resourcePaths[index]); + urls[index] = I18nClassloaderTest.class.getResource(resourcePaths[index]); } return new URLClassLoader(urls); } diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java new file mode 100644 index 00000000000..5d7923d23bd --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java @@ -0,0 +1,392 @@ +/* + * 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 org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; +import org.picocontainer.injectors.ProviderAdapter; +import org.sonar.api.Property; +import org.sonar.api.config.PropertyDefinitions; + +import java.util.Arrays; + +import static junit.framework.Assert.assertTrue; +import static junit.framework.Assert.fail; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +public class ComponentContainerTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void shouldRegisterItself() { + ComponentContainer container = new ComponentContainer(); + assertThat(container.getComponentByType(ComponentContainer.class)).isSameAs(container); + } + + @Test + public void should_start_and_stop() { + ComponentContainer container = spy(new ComponentContainer()); + container.addSingleton(StartableComponent.class); + container.startComponents(); + + assertThat(container.getComponentByType(StartableComponent.class).started).isTrue(); + assertThat(container.getComponentByType(StartableComponent.class).stopped).isFalse(); + verify(container).doBeforeStart(); + verify(container).doAfterStart(); + + container.stopComponents(); + assertThat(container.getComponentByType(StartableComponent.class).stopped).isTrue(); + } + + @Test + public void should_start_and_stop_hierarchy_of_containers() { + StartableComponent parentComponent = new StartableComponent(); + final StartableComponent childComponent = new StartableComponent(); + ComponentContainer parentContainer = new ComponentContainer() { + @Override + public void doAfterStart() { + ComponentContainer childContainer = new ComponentContainer(this); + childContainer.add(childComponent); + childContainer.execute(); + } + }; + parentContainer.add(parentComponent); + parentContainer.execute(); + assertThat(parentComponent.started).isTrue(); + assertThat(parentComponent.stopped).isTrue(); + assertThat(childComponent.started).isTrue(); + assertThat(childComponent.stopped).isTrue(); + } + + @Test + public void should_stop_hierarchy_of_containers_on_failure() { + StartableComponent parentComponent = new StartableComponent(); + final StartableComponent childComponent1 = new StartableComponent(); + final UnstartableComponent childComponent2 = new UnstartableComponent(); + ComponentContainer parentContainer = new ComponentContainer() { + @Override + public void doAfterStart() { + ComponentContainer childContainer = new ComponentContainer(this); + childContainer.add(childComponent1); + childContainer.add(childComponent2); + childContainer.execute(); + } + }; + parentContainer.add(parentComponent); + try { + parentContainer.execute(); + fail(); + } catch (Exception e) { + assertThat(parentComponent.started).isTrue(); + assertThat(parentComponent.stopped).isTrue(); + assertThat(childComponent1.started).isTrue(); + assertThat(childComponent1.stopped).isTrue(); + } + } + + @Test + public void testChild() { + ComponentContainer parent = new ComponentContainer(); + parent.startComponents(); + + ComponentContainer child = parent.createChild(); + child.addSingleton(StartableComponent.class); + child.startComponents(); + + assertThat(child.getParent()).isSameAs(parent); + assertThat(parent.getChild()).isSameAs(child); + assertThat(child.getComponentByType(ComponentContainer.class)).isSameAs(child); + assertThat(parent.getComponentByType(ComponentContainer.class)).isSameAs(parent); + assertThat(child.getComponentByType(StartableComponent.class)).isNotNull(); + assertThat(parent.getComponentByType(StartableComponent.class)).isNull(); + + parent.stopComponents(); + } + + @Test + public void testRemoveChild() { + ComponentContainer parent = new ComponentContainer(); + parent.startComponents(); + + ComponentContainer child = parent.createChild(); + assertThat(parent.getChild()).isSameAs(child); + + parent.removeChild(); + assertThat(parent.getChild()).isNull(); + } + + @Test + public void shouldForwardStartAndStopToDescendants() { + ComponentContainer grandParent = new ComponentContainer(); + ComponentContainer parent = grandParent.createChild(); + ComponentContainer child = parent.createChild(); + child.addSingleton(StartableComponent.class); + + grandParent.startComponents(); + + StartableComponent component = child.getComponentByType(StartableComponent.class); + assertTrue(component.started); + + parent.stopComponents(); + assertTrue(component.stopped); + } + + @Test + public void shouldDeclareComponentProperties() { + ComponentContainer container = new ComponentContainer(); + container.addSingleton(ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(propertyDefinitions.get("foo").defaultValue()).isEqualTo("bar"); + } + + @Test + public void shouldDeclareExtensionWithoutAddingIt() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + container.declareExtension(plugin, ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNull(); + } + + @Test + public void shouldDeclareExtensionWhenAdding() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + container.addExtension(plugin, ComponentWithProperty.class); + + PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); + assertThat(propertyDefinitions.get("foo")).isNotNull(); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByKey(ComponentWithProperty.class)).isNotNull(); + } + + @Test + public void test_add_class() { + ComponentContainer container = new ComponentContainer(); + container.add(ComponentWithProperty.class, SimpleComponent.class); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void test_add_collection() { + ComponentContainer container = new ComponentContainer(); + container.add(Arrays.asList(ComponentWithProperty.class, SimpleComponent.class)); + assertThat(container.getComponentByType(ComponentWithProperty.class)).isNotNull(); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void test_add_adapter() { + ComponentContainer container = new ComponentContainer(); + container.add(new SimpleComponentProvider()); + assertThat(container.getComponentByType(SimpleComponent.class)).isNotNull(); + } + + @Test + public void should_sanitize_pico_exception_on_start_failure() { + ComponentContainer container = new ComponentContainer(); + container.add(UnstartableComponent.class); + + // do not expect a PicoException + thrown.expect(IllegalStateException.class); + container.startComponents(); + } + + @Test + public void display_plugin_name_when_failing_to_add_extension() { + ComponentContainer container = new ComponentContainer(); + PluginInfo plugin = mock(PluginInfo.class); + + container.startComponents(); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent"); + + container.addExtension(plugin, UnstartableComponent.class); + + } + + @Test + public void test_start_failure() { + ComponentContainer container = new ComponentContainer(); + StartableComponent startable = new StartableComponent(); + container.add(startable, UnstartableComponent.class); + + try { + container.execute(); + fail(); + } catch (Exception e) { + assertThat(startable.started).isTrue(); + + // container stops the components that have already been started + assertThat(startable.stopped).isTrue(); + } + } + + @Test + public void test_stop_failure() { + ComponentContainer container = new ComponentContainer(); + StartableComponent startable = new StartableComponent(); + container.add(startable, UnstoppableComponent.class); + + try { + container.execute(); + fail(); + } catch (Exception e) { + assertThat(startable.started).isTrue(); + + // container should stop the components that have already been started + // ... but that's not the case + } + } + + @Test + public void stop_exception_should_not_hide_start_exception() { + ComponentContainer container = new ComponentContainer(); + container.add(UnstartableComponent.class, UnstoppableComponent.class); + + thrown.expect(IllegalStateException.class); + thrown.expectMessage("Fail to start"); + container.execute(); + } + + @Test + public void should_execute_components() { + ComponentContainer container = new ComponentContainer(); + StartableComponent component = new StartableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.started).isTrue(); + assertThat(component.stopped).isTrue(); + } + + /** + * Method close() must be called even if the methods start() or stop() + * are not defined. + */ + @Test + public void should_close_components_without_lifecycle() { + ComponentContainer container = new ComponentContainer(); + CloseableComponent component = new CloseableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.isClosed).isTrue(); + } + + /** + * Method close() must be executed after stop() + */ + @Test + public void should_close_components_with_lifecycle() { + ComponentContainer container = new ComponentContainer(); + StartableCloseableComponent component = new StartableCloseableComponent(); + container.add(component); + + container.execute(); + + assertThat(component.isStopped).isTrue(); + assertThat(component.isClosed).isTrue(); + assertThat(component.isClosedAfterStop).isTrue(); + } + + public static class StartableComponent { + public boolean started = false, stopped = false; + + public void start() { + started = true; + } + + public void stop() { + stopped = true; + } + } + + public static class UnstartableComponent { + public void start() { + throw new IllegalStateException("Fail to start"); + } + + public void stop() { + + } + } + + public static class UnstoppableComponent { + public void start() { + } + + public void stop() { + throw new IllegalStateException("Fail to stop"); + } + } + + @Property(key = "foo", defaultValue = "bar", name = "Foo") + public static class ComponentWithProperty { + + } + + public static class SimpleComponent { + + } + + public static class SimpleComponentProvider extends ProviderAdapter { + public SimpleComponent provide() { + return new SimpleComponent(); + } + } + + public static class CloseableComponent implements AutoCloseable { + public boolean isClosed = false; + + @Override + public void close() throws Exception { + isClosed = true; + } + } + + public static class StartableCloseableComponent implements AutoCloseable { + public boolean isClosed = false, isStopped = false, isClosedAfterStop = false; + + public void stop() { + isStopped = true; + } + + @Override + public void close() throws Exception { + isClosed = true; + isClosedAfterStop = isStopped; + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java new file mode 100644 index 00000000000..431890bc012 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java @@ -0,0 +1,80 @@ +/* + * 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 org.junit.Test; +import org.sonar.api.utils.log.Logger; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; + +public class ComponentKeysTest { + + ComponentKeys keys = new ComponentKeys(); + + @Test + public void generate_key_of_class() { + assertThat(keys.of(FakeComponent.class)).isEqualTo(FakeComponent.class); + } + + @Test + public void generate_key_of_object() throws Exception { + assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.core.platform.ComponentKeysTest.FakeComponent-fake"); + } + + @Test + public void should_log_warning_if_toString_is_not_overridden() { + Logger log = mock(Logger.class); + keys.of(new Object(), log); + verifyZeroInteractions(log); + + // only on non-first runs, to avoid false-positives on singletons + keys.of(new Object(), log); + verify(log).warn(startsWith("Bad component key")); + } + + @Test + public void should_generate_unique_key_when_toString_is_not_overridden() { + Object key = keys.of(new WrongToStringImpl()); + assertThat(key).isNotEqualTo(WrongToStringImpl.KEY); + + Object key2 = keys.of(new WrongToStringImpl()); + assertThat(key2).isNotEqualTo(key); + } + + static class FakeComponent { + @Override + public String toString() { + return "fake"; + } + } + + static class WrongToStringImpl { + static final String KEY = "my.Component@123a"; + + @Override + public String toString() { + return KEY; + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java new file mode 100644 index 00000000000..67b488584a9 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java @@ -0,0 +1,106 @@ +/* + * 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 org.junit.Test; +import org.picocontainer.Characteristics; +import org.picocontainer.MutablePicoContainer; +import org.picocontainer.PicoLifecycleException; + +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class PicoUtilsTest { + + @Test + public void shouldSanitizePicoLifecycleException() { + Throwable th = PicoUtils.sanitize(newPicoLifecycleException(false)); + + assertThat(th).isInstanceOf(IllegalStateException.class); + assertThat(th.getMessage()).isEqualTo("A good reason to fail"); + } + + @Test + public void shouldSanitizePicoLifecycleException_no_wrapper_message() { + Throwable th = PicoUtils.sanitize(new PicoLifecycleException(null, null, new IllegalStateException("msg"))); + + assertThat(th).isInstanceOf(IllegalStateException.class); + assertThat(th.getMessage()).isEqualTo("msg"); + } + + @Test + public void shouldNotSanitizeOtherExceptions() { + Throwable th = PicoUtils.sanitize(new IllegalArgumentException("foo")); + + assertThat(th).isInstanceOf(IllegalArgumentException.class); + assertThat(th.getMessage()).isEqualTo("foo"); + } + + @Test + public void shouldPropagateInitialUncheckedException() { + try { + PicoUtils.propagate(newPicoLifecycleException(false)); + fail(); + } catch (RuntimeException e) { + assertThat(e).isInstanceOf(IllegalStateException.class); + } + } + + @Test + public void shouldThrowUncheckedExceptionWhenPropagatingCheckedException() { + try { + PicoUtils.propagate(newPicoLifecycleException(true)); + fail(); + } catch (RuntimeException e) { + assertThat(e.getCause()).isInstanceOf(IOException.class); + assertThat(e.getCause().getMessage()).isEqualTo("Checked"); + } + } + + private PicoLifecycleException newPicoLifecycleException(boolean initialCheckedException) { + MutablePicoContainer container = ComponentContainer.createPicoContainer().as(Characteristics.CACHE); + if (initialCheckedException) { + container.addComponent(CheckedFailureComponent.class); + } else { + container.addComponent(UncheckedFailureComponent.class); + } + try { + container.start(); + return null; + + } catch (PicoLifecycleException e) { + return e; + } + } + + public static class UncheckedFailureComponent { + public void start() { + throw new IllegalStateException("A good reason to fail"); + } + } + + public static class CheckedFailureComponent { + public void start() throws IOException { + throw new IOException("Checked"); + } + } +} 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 new file mode 100644 index 00000000000..2b702748c4b --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java @@ -0,0 +1,203 @@ +/* + * 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 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.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; +import static org.junit.Assert.fail; + +public class PluginInfoTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void test_RequiredPlugin() throws Exception { + PluginInfo.RequiredPlugin plugin = PluginInfo.RequiredPlugin.parse("java:1.1"); + assertThat(plugin.getKey()).isEqualTo("java"); + assertThat(plugin.getMinimalVersion().getName()).isEqualTo("1.1"); + + try { + PluginInfo.RequiredPlugin.parse("java"); + fail(); + } catch (IllegalArgumentException expected) { + // ok + } + } + + @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); + 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); + } + + @Test + public void test_compatibility_with_sq_version() { + 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(); + + assertThat(withMinSqVersion("1.0").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); + assertThat(withMinSqVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); + + assertThat(withMinSqVersion("1.1").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); + assertThat(withMinSqVersion("2.10").isCompatibleWith("2.1")).isFalse(); + assertThat(withMinSqVersion("10.10").isCompatibleWith("2.2")).isFalse(); + + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); + assertThat(withMinSqVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + + assertThat(withMinSqVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); + + assertThat(withMinSqVersion(null).isCompatibleWith("0")).isTrue(); + assertThat(withMinSqVersion(null).isCompatibleWith("3.1")).isTrue(); + } + + @Test + public void create_from_minimal_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("java"); + manifest.setVersion("1.0"); + manifest.setName("Java"); + manifest.setMainClass("org.foo.FooPlugin"); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getKey()).isEqualTo("java"); + assertThat(pluginInfo.getName()).isEqualTo("Java"); + assertThat(pluginInfo.getVersion().getName()).isEqualTo("1.0"); + assertThat(pluginInfo.getFile()).isSameAs(jarFile); + assertThat(pluginInfo.getMainClass()).isEqualTo("org.foo.FooPlugin"); + + // optional fields + assertThat(pluginInfo.getBasePlugin()).isNull(); + assertThat(pluginInfo.getDescription()).isNull(); + assertThat(pluginInfo.getHomepageUrl()).isNull(); + assertThat(pluginInfo.getImplementationBuild()).isNull(); + assertThat(pluginInfo.getIssueTrackerUrl()).isNull(); + assertThat(pluginInfo.getLicense()).isNull(); + assertThat(pluginInfo.getOrganizationName()).isNull(); + assertThat(pluginInfo.getOrganizationUrl()).isNull(); + assertThat(pluginInfo.getMinimalSqVersion()).isNull(); + assertThat(pluginInfo.getRequiredPlugins()).isEmpty(); + } + + @Test + public void create_from_complete_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("fbcontrib"); + manifest.setVersion("2.0"); + manifest.setName("Java"); + manifest.setMainClass("org.fb.FindbugsPlugin"); + manifest.setBasePlugin("findbugs"); + manifest.setSonarVersion("4.5.1"); + manifest.setDescription("the desc"); + manifest.setHomepage("http://fbcontrib.org"); + manifest.setImplementationBuild("SHA1"); + manifest.setLicense("LGPL"); + manifest.setOrganization("SonarSource"); + manifest.setOrganizationUrl("http://sonarsource.com"); + manifest.setIssueTrackerUrl("http://jira.com"); + manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"}); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getBasePlugin()).isEqualTo("findbugs"); + assertThat(pluginInfo.getDescription()).isEqualTo("the desc"); + assertThat(pluginInfo.getHomepageUrl()).isEqualTo("http://fbcontrib.org"); + assertThat(pluginInfo.getImplementationBuild()).isEqualTo("SHA1"); + assertThat(pluginInfo.getIssueTrackerUrl()).isEqualTo("http://jira.com"); + assertThat(pluginInfo.getLicense()).isEqualTo("LGPL"); + assertThat(pluginInfo.getOrganizationName()).isEqualTo("SonarSource"); + assertThat(pluginInfo.getOrganizationUrl()).isEqualTo("http://sonarsource.com"); + assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo("4.5.1"); + assertThat(pluginInfo.getRequiredPlugins()).extracting("key").containsExactly("java", "pmd"); + } + + @Test + public void create_from_file() throws Exception { + File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar")); + PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar); + + assertThat(checkstyleInfo.getName()).isEqualTo("Checkstyle"); + assertThat(checkstyleInfo.getMinimalSqVersion()).isEqualTo(Version.create("2.8")); + } + + @Test + public void test_toString() throws Exception { + PluginInfo pluginInfo = new PluginInfo().setKey("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 pluginInfo = new PluginInfo("foo"); + if (version != null) { + pluginInfo.setMinimalSqVersion(Version.create(version)); + } + return pluginInfo; + } +} 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 new file mode 100644 index 00000000000..7cb984b1bda --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java @@ -0,0 +1,131 @@ +/* + * 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.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.assertj.core.data.MapEntry; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.Plugin; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class PluginLoaderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void complete_test() 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()); + Map<String, Plugin> instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo)); + + assertThat(instances).containsOnlyKeys("checkstyle"); + Plugin checkstyleInstance = instances.get("checkstyle"); + assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin"); + + loader.unload(instances.values()); + // should test that classloaders are closed + } + + @Test + public void define_plugin_classloader__nominal() throws Exception { + PluginInfo info = new PluginInfo("foo") + .setName("Foo") + .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)); + + 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")); + // TODO test mask - require change in sonar-classloader + } + + @Test + public void define_plugin_classloader__extend_base_plugin() throws Exception { + File baseJarFile = temp.newFile(), extensionJarFile = temp.newFile(); + PluginInfo base = new PluginInfo("foo") + .setName("Foo") + .setMainClass("org.foo.FooPlugin") + .setFile(baseJarFile); + PluginInfo extension = new PluginInfo("fooContrib") + .setName("Foo Contrib") + .setMainClass("org.foo.ContribPlugin") + .setFile(extensionJarFile) + .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)); + + 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")); + // TODO test mask - require change in sonar-classloader + } + + /** + * Does not unzip jar file. + */ + private class BasicPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return new UnzippedPlugin(info.getKey(), info.getFile(), Collections.<File>emptyList()); + } + } + + private class TempPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + File tempDir = temp.newFolder(); + ZipUtils.unzip(info.getFile(), tempDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), tempDir); + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java new file mode 100644 index 00000000000..cbdd9c23356 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java @@ -0,0 +1,81 @@ +/* + * 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 org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginUnzipperTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + 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); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + ZipUtils.unzip(jarFile, toDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), 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); + } + + @Test + public void unzip_plugin_without_libs() throws Exception { + File jarFile = temp.newFile(); + final File toDir = temp.newFolder(); + PluginInfo pluginInfo = new PluginInfo().setFile(jarFile); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir); + } + }; + UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); + assertThat(unzipped.getKey()).isEqualTo("foo"); + assertThat(unzipped.getLibs()).isEmpty(); + assertThat(unzipped.getMain()).isSameAs(jarFile); + } + + private File getFile(String filename) { + return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java deleted file mode 100644 index 98cdf2698b6..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.plugins; - -import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Ordering.natural; -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultPluginMetadataTest { - - @Test - public void testGettersAndSetters() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")); - metadata.setKey("checkstyle") - .setLicense("LGPL") - .setDescription("description") - .setHomepage("http://home") - .setIssueTrackerUrl("http://jira.codehuas.org") - .setMainClass("org.Main") - .setOrganization("SonarSource") - .setOrganizationUrl("http://sonarsource.org") - .setVersion("1.1") - .setSonarVersion("3.0") - .setUseChildFirstClassLoader(true) - .setCore(false) - .setImplementationBuild("abcdef"); - - assertThat(metadata.getKey()).isEqualTo("checkstyle"); - assertThat(metadata.getParent()).isNull(); - assertThat(metadata.getLicense()).isEqualTo("LGPL"); - assertThat(metadata.getDescription()).isEqualTo("description"); - assertThat(metadata.getHomepage()).isEqualTo("http://home"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehuas.org"); - assertThat(metadata.getMainClass()).isEqualTo("org.Main"); - assertThat(metadata.getOrganization()).isEqualTo("SonarSource"); - assertThat(metadata.getOrganizationUrl()).isEqualTo("http://sonarsource.org"); - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("3.0"); - assertThat(metadata.isUseChildFirstClassLoader()).isTrue(); - assertThat(metadata.isCore()).isFalse(); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getFile()).isNotNull(); - assertThat(metadata.getDeployedFiles()).isEmpty(); - assertThat(metadata.getImplementationBuild()).isEqualTo("abcdef"); - } - - @Test - public void testDeployedFiles() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .addDeployedFile(new File("foo.jar")) - .addDeployedFile(new File("bar.jar")); - - assertThat(metadata.getDeployedFiles()).hasSize(2); - } - - @Test - public void testInternalPathToDependencies() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setPathsToInternalDeps(newArrayList("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar")); - - assertThat(metadata.getPathsToInternalDeps()).containsOnly("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar"); - } - - @Test - public void shouldEquals() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle"); - PluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")).setKey("pmd"); - - assertThat(checkstyle).isEqualTo(checkstyle); - assertThat(checkstyle).isEqualTo(DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle")); - assertThat(checkstyle).isNotEqualTo(pmd); - } - - @Test - public void shouldCompare() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setKey("checkstyle") - .setName("Checkstyle"); - DefaultPluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")) - .setKey("pmd") - .setName("PMD"); - List<DefaultPluginMetadata> plugins = Arrays.asList(pmd, checkstyle); - - assertThat(natural().sortedCopy(plugins)).extracting("key").containsExactly("checkstyle", "pmd"); - } - - @Test - public void should_check_compatibility_with_sonar_version() { - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1.0")).isTrue(); - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.0.0")).isTrue(); - - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); - assertThat(pluginWithVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); - - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); - assertThat(pluginWithVersion("2.10").isCompatibleWith("2.1")).isFalse(); - assertThat(pluginWithVersion("10.10").isCompatibleWith("2.2")).isFalse(); - - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); - assertThat(pluginWithVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - - assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); - - assertThat(pluginWithVersion(null).isCompatibleWith("0")).isTrue(); - assertThat(pluginWithVersion(null).isCompatibleWith("3.1")).isTrue(); - } - - static DefaultPluginMetadata pluginWithVersion(String version) { - return DefaultPluginMetadata.create("foo").setSonarVersion(version); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java deleted file mode 100644 index d9b3ada4254..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java +++ /dev/null @@ -1,129 +0,0 @@ -/* - * 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.plugins; - -import org.apache.commons.io.FileUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.util.Arrays; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PluginClassloadersTest { - - private PluginClassloaders classloaders; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void before() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - } - - @After - public void clean() { - if (classloaders != null) { - classloaders.clean(); - } - } - - @Test - public void shouldImport() { - classloaders.add(DefaultPluginMetadata.create("foo").addDeployedFile(getFile("PluginClassloadersTest/foo.jar"))); - classloaders.add(DefaultPluginMetadata.create("bar").addDeployedFile(getFile("PluginClassloadersTest/bar.jar"))); - classloaders.done(); - - String resourceName = "org/sonar/plugins/bar/api/resource.txt"; - assertThat(classloaders.get("bar").getResourceAsStream(resourceName)).isNotNull(); - assertThat(classloaders.get("foo").getResourceAsStream(resourceName)).isNotNull(); - } - - @Test - public void shouldCreateBaseClassloader() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - Map<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>asList(checkstyle)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - ClassRealm checkstyleRealm = (ClassRealm) checkstyleEntryPoint.getClass().getClassLoader(); - assertThat(checkstyleRealm.getId()).isEqualTo("checkstyle"); - } - - @Test - public void shouldExtendPlugin() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - DefaultPluginMetadata checkstyleExt = DefaultPluginMetadata.create("checkstyle-ext") - .setBasePlugin("checkstyle") - .setMainClass("com.mycompany.sonar.checkstyle.CheckstyleExtensionsPlugin") - .addDeployedFile(getFile("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); - - Map<String, Plugin> map = classloaders.init(Arrays.<PluginMetadata>asList(checkstyle, checkstyleExt)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - Plugin checkstyleExtEntryPoint = map.get("checkstyle-ext"); - - assertThat(checkstyleEntryPoint.getClass().getClassLoader().equals(checkstyleExtEntryPoint.getClass().getClassLoader())).isTrue(); - } - - @Test - public void detect_plugins_compiled_for_bad_java_version() throws Exception { - thrown.expect(SonarException.class); - thrown.expectMessage("The plugin checkstyle is not supported with Java 1."); - - ClassWorld world = mock(ClassWorld.class); - when(world.newRealm(anyString(), any(ClassLoader.class))).thenThrow(new UnsupportedClassVersionError()); - - classloaders = new PluginClassloaders(getClass().getClassLoader(), world); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - classloaders.init(Arrays.<PluginMetadata>asList(checkstyle)); - } - - private File getFile(String filename) { - return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java deleted file mode 100644 index b66bf864085..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java +++ /dev/null @@ -1,99 +0,0 @@ -/* - * 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.plugins; - -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PluginJarInstallerTest { - - private PluginJarInstaller extractor; - - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File userHome; - - @Before - public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new PluginJarInstaller() { - @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return null; - } - }; - } - - @Test - public void should_extract_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-cobertura-plugin-3.1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("cobertura"); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getName()).isEqualTo("Cobertura"); - assertThat(metadata.isCore()).isEqualTo(true); - assertThat(metadata.getFile().getName()).isEqualTo("sonar-cobertura-plugin-3.1.1.jar"); - assertThat(metadata.getVersion()).isEqualTo("3.1.1"); - assertThat(metadata.getImplementationBuild()).isEqualTo("b9283404030db9ce1529b1fadfb98331686b116d"); - assertThat(metadata.getHomepage()).isEqualTo("http://www.sonarsource.org/plugins/sonar-cobertura-plugin"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehaus.org/browse/SONAR"); - } - - @Test - public void should_read_sonar_version() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-switch-off-violations-plugin-1.1.jar"), false); - - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("2.5"); - } - - @Test - public void should_extract_extension_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("checkstyleextensions"); - assertThat(metadata.getBasePlugin()).isEqualTo("checkstyle"); - } - - @Test - public void should_extract_requires_plugin_information() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("fake2-plugin-1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("fake2"); - assertThat(metadata.getRequiredPlugins().get(0)).isEqualTo("fake1:1.1"); - } - - File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(PluginJarInstallerTest.class.getResource("/org/sonar/core/plugins/" + filename)); - File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); - FileUtils.copyFile(src, destFile); - return destFile; - } - -} diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar Binary files differdeleted file mode 100644 index 343ad65f133..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar +++ /dev/null diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar Binary files differdeleted file mode 100644 index 505311c008b..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar +++ /dev/null diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml b/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml deleted file mode 100644 index 75a263db3c3..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml +++ /dev/null @@ -1 +0,0 @@ -<fake/>
\ No newline at end of file diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar Binary files differdeleted file mode 100644 index f4b8b79b776..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar +++ /dev/null diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar Binary files differdeleted file mode 100644 index 4ae5393cee5..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar +++ /dev/null diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar Binary files differdeleted file mode 100644 index 6a74b55d02c..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar +++ /dev/null diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar Binary files differdeleted file mode 100644 index 8044dff8988..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar +++ /dev/null |