aboutsummaryrefslogtreecommitdiffstats
path: root/sonar-core
diff options
context:
space:
mode:
Diffstat (limited to 'sonar-core')
-rw-r--r--sonar-core/pom.xml14
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java59
-rw-r--r--sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java36
-rw-r--r--sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/notification/package-info.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java250
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java52
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java47
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java353
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java199
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java (renamed from sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java)36
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java (renamed from sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java)26
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java62
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/package-info.java27
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java310
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java253
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java111
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java3
-rw-r--r--sonar-core/src/main/java/org/sonar/core/plugins/package-info.java4
-rw-r--r--sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java12
-rw-r--r--sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java4
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java392
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java80
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java106
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java203
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java131
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java81
-rw-r--r--sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java145
-rw-r--r--sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java129
-rw-r--r--sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java99
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jarbin1057 -> 0 bytes
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jarbin313 -> 0 bytes
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml1
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jarbin2673 -> 0 bytes
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jarbin5597 -> 0 bytes
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jarbin14560 -> 0 bytes
-rw-r--r--sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jarbin12689 -> 0 bytes
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
deleted file mode 100644
index 343ad65f133..00000000000
--- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index 505311c008b..00000000000
--- a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar
+++ /dev/null
Binary files differ
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
deleted file mode 100644
index f4b8b79b776..00000000000
--- a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar
+++ /dev/null
Binary files differ
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
deleted 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
Binary files differ
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
deleted 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
Binary files differ
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
deleted 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
Binary files differ