<dependency>
<groupId>org.codehaus.sonar</groupId>
<artifactId>sonar-update-center-common</artifactId>
- <version>1.0</version>
+ <version>1.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.codehaus.sonar</groupId>
private ClassLoadersCollection classLoaders;
private ExtensionDownloader extensionDownloader;
private EnvironmentInformation environment;
+ private List<JpaPlugin> register;
public BatchPluginRepository(JpaPluginDao dao, ExtensionDownloader extensionDownloader, EnvironmentInformation environment) {
this.dao = dao;
* for unit tests only
*/
BatchPluginRepository() {
+ }
+
+ private List<URL> download(JpaPlugin pluginMetadata) {
+ List<URL> urls = Lists.newArrayList();
+ for (JpaPluginFile pluginFile : pluginMetadata.getFiles()) {
+ File file = extensionDownloader.downloadExtension(pluginFile);
+ try {
+ urls.add(file.toURI().toURL());
+ } catch (MalformedURLException e) {
+ throw new SonarException("Can not get the URL of: " + file, e);
+ }
+ }
+ return urls;
}
public void start() {
+ register = Lists.newArrayList();
classLoaders = new ClassLoadersCollection(Thread.currentThread().getContextClassLoader());
- for (JpaPlugin pluginMetadata : dao.getPlugins()) {
- String key = pluginMetadata.getKey();
- List<URL> urls = Lists.newArrayList();
- for (JpaPluginFile pluginFile : pluginMetadata.getFiles()) {
- File file = extensionDownloader.downloadExtension(pluginFile);
- try {
- urls.add(file.toURI().toURL());
-
- } catch (MalformedURLException e) {
- throw new SonarException("Can not get the URL of: " + file, e);
- }
+
+ List<JpaPlugin> jpaPlugins = dao.getPlugins();
+
+ for (JpaPlugin pluginMetadata : jpaPlugins) {
+ if (StringUtils.isEmpty(pluginMetadata.getBasePlugin())) {
+ String key = pluginMetadata.getKey();
+ List<URL> urls = download(pluginMetadata);
+ classLoaders.createClassLoader(key, urls, pluginMetadata.isUseChildFirstClassLoader() == Boolean.TRUE);
+ register.add(pluginMetadata);
}
- if (LOG.isDebugEnabled()) {
- LOG.debug("Classloader of plugin " + key + ":");
- for (URL url : urls) {
- LOG.debug(" -> " + url);
+ }
+
+ // Extend plugins by other plugins
+ for (JpaPlugin pluginMetadata : jpaPlugins) {
+ String pluginKey = pluginMetadata.getKey();
+ String basePluginKey = pluginMetadata.getBasePlugin();
+ if (StringUtils.isNotEmpty(basePluginKey)) {
+ if (classLoaders.get(basePluginKey) != null) {
+ LOG.debug("Plugin {} extends {}", pluginKey, basePluginKey);
+ List<URL> urls = download(pluginMetadata);
+ classLoaders.extend(basePluginKey, pluginKey, urls);
+ register.add(pluginMetadata);
+ } else {
+ // Ignored, because base plugin doesn't exists
+ LOG.warn("Plugin {} extends nonexistent plugin {}", pluginKey, basePluginKey);
}
}
- classLoaders.createClassLoader(key, urls, pluginMetadata.isUseChildFirstClassLoader() == Boolean.TRUE);
}
+
classLoaders.done();
}
public void registerPlugins(MutablePicoContainer pico) {
- for (JpaPlugin pluginMetadata : dao.getPlugins()) {
+ for (JpaPlugin pluginMetadata : register) {
try {
Class claz = classLoaders.get(pluginMetadata.getKey()).loadClass(pluginMetadata.getPluginClass());
Plugin plugin = (Plugin) claz.newInstance();
import java.util.Collection;
import java.util.List;
+import com.google.common.collect.Lists;
import org.apache.commons.lang.StringUtils;
import org.codehaus.plexus.classworlds.ClassWorld;
import org.codehaus.plexus.classworlds.realm.ClassRealm;
import org.sonar.api.utils.Logs;
import org.sonar.api.utils.SonarException;
-import com.google.common.collect.Lists;
-
/**
* Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on
* {@link ClassWorld}.
}
}
+ public void extend(String baseKey, String key, Collection<URL> urls) {
+ try {
+ ClassRealm base = world.getRealm(baseKey);
+ base.createChildRealm(key); // we create new realm to be able to return it by key without conversion to baseKey
+ for (URL url : urls) {
+ base.addURL(url);
+ }
+ } catch (NoSuchRealmException e) {
+ throw new SonarException(e);
+ } catch (DuplicateRealmException e) {
+ throw new SonarException(e);
+ }
+ }
+
/**
* Establishes dependencies among ClassLoaders.
*/
public void done() {
for (Object o : world.getRealms()) {
ClassRealm realm = (ClassRealm) o;
- if ( !StringUtils.endsWith(realm.getId(), "-parent")) {
+ 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
Logs.INFO.debug("Exporting " + Arrays.toString(packages) + " from " + realm.getId());
for (Object o : world.getRealms()) {
ClassRealm dep = (ClassRealm) o;
- if ( !StringUtils.equals(dep.getId(), realm.getId())) {
+ if (!StringUtils.equals(dep.getId(), realm.getId())) {
try {
for (String packageName : packages) {
dep.importFrom(realm.getId(), packageName);
@Column(name = "core", updatable = true, nullable = true)
private Boolean core;
-
+
@Column(name = "child_first_classloader", updatable = true, nullable = true)
private Boolean childFirstClassLoader = Boolean.FALSE;
- @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
+ @Column(name = "base_plugin", updatable = true, nullable = true)
+ private String basePlugin;
+
+ @Cascade({ org.hibernate.annotations.CascadeType.SAVE_UPDATE,
org.hibernate.annotations.CascadeType.DELETE,
org.hibernate.annotations.CascadeType.MERGE,
org.hibernate.annotations.CascadeType.PERSIST,
- org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
- @OneToMany(mappedBy = "plugin", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
+ org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
+ @OneToMany(mappedBy = "plugin", cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
private List<JpaPluginFile> files = new ArrayList<JpaPluginFile>();
public JpaPlugin() {
return this;
}
+ public String getBasePlugin() {
+ return basePlugin;
+ }
+
+ public void setBasePlugin(String basePlugin) {
+ this.basePlugin = basePlugin;
+ }
+
public void createFile(String filename) {
JpaPluginFile file = new JpaPluginFile(this, filename);
this.files.add(file);
public class SchemaMigration {
public final static int VERSION_UNKNOWN = -1;
- public static final int LAST_VERSION = 180;
+ public static final int LAST_VERSION = 181;
public final static String TABLE_NAME = "schema_migrations";
<dataset>
<plugins id="1" name="Checkstyle" plugin_key="checkstyle" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" version="2.2" />
+ description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" base_plugin="[null]" version="2.2" />
<plugin_files id="3" plugin_id="1" filename="newfile.jar"/>
</dataset>
\ No newline at end of file
<dataset>
<plugins id="1" name="Checkstyle" plugin_key="checkstyle" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" version="2.2"/>
+ description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" base_plugin="[null]" version="2.2"/>
<plugins id="2" name="PMD" plugin_key="pmd" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="org.sonar.pmd.Main" core="false" child_first_classloader="false" version="[null]" />
+ description="[null]" installation_date="[null]" plugin_class="org.sonar.pmd.Main" core="false" child_first_classloader="false" base_plugin="[null]" version="[null]" />
<plugin_files id="1" plugin_id="1" filename="checkstyle.jar"/>
<plugin_files id="2" plugin_id="1" filename="checkstyle-extension.jar"/>
<dataset>
<plugins id="1" name="Checkstyle" plugin_key="checkstyle" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" version="2.2"/>
+ description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" base_plugin="[null]" version="2.2"/>
<plugins id="2" name="PMD" plugin_key="pmd" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="org.sonar.pmd.Main" core="false" child_first_classloader="false" version="2.2" />
+ description="[null]" installation_date="[null]" plugin_class="org.sonar.pmd.Main" core="false" child_first_classloader="false" base_plugin="[null]" version="2.2" />
<plugin_files id="1" plugin_id="1" filename="checkstyle.jar"/>
<plugin_files id="2" plugin_id="1" filename="checkstyle-extension.jar"/>
<dataset>
<plugins id="1" name="Checkstyle" plugin_key="checkstyle" organization="[null]" organization_url="[null]" license="[null]" homepage="[null]"
- description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" version="2.2" />
+ description="[null]" installation_date="[null]" plugin_class="[null]" core="true" child_first_classloader="false" base_plugin="[null]" version="2.2" />
<plugin_files id="1" plugin_id="1" filename="checkstyle.jar"/>
<plugin_files id="2" plugin_id="1" filename="checkstyle-extension.jar"/>
*/
package org.sonar.server.plugins;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.LoggerFactory;
-import org.sonar.api.ServerComponent;
-import org.sonar.core.classloaders.ClassLoadersCollection;
-
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Collection;
import java.util.List;
+import com.google.common.collect.Lists;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.utils.Logs;
+import org.sonar.core.classloaders.ClassLoadersCollection;
+
public class PluginClassLoaders implements ServerComponent {
private ClassLoadersCollection classLoaders = new ClassLoadersCollection(getClass().getClassLoader());
- public ClassLoader create(PluginMetadata plugin) {
- return create(plugin.getKey(), plugin.getDeployedFiles(), plugin.isUseChildFirstClassLoader());
+ private List<PluginMetadata> metadata = Lists.newArrayList();
+
+ public void addForCreation(PluginMetadata plugin) {
+ metadata.add(plugin);
}
ClassLoader create(String pluginKey, Collection<File> classloaderFiles, boolean useChildFirstClassLoader) {
}
}
+ private void extend(String basePluginKey, String pluginKey, Collection<File> classloaderFiles) {
+ try {
+ List<URL> urls = Lists.newArrayList();
+ for (File file : classloaderFiles) {
+ urls.add(toUrl(file));
+ }
+ classLoaders.extend(basePluginKey, pluginKey, urls);
+ } catch (MalformedURLException e) {
+ throw new RuntimeException("Fail to load the classloader of the plugin: " + pluginKey, e);
+ }
+ }
+
URL toUrl(File file) throws MalformedURLException {
// From Classworlds javadoc :
// A constituent is a URL that points to either a JAR format file containing
return clazz;
}
- public void done() {
+ public List<PluginMetadata> completeCreation() {
+ List<PluginMetadata> created = Lists.newArrayList();
+ for (PluginMetadata pluginMetadata : metadata) {
+ if (StringUtils.isEmpty(pluginMetadata.getBasePlugin())) {
+ create(pluginMetadata.getKey(), pluginMetadata.getDeployedFiles(), pluginMetadata.isUseChildFirstClassLoader());
+ created.add(pluginMetadata);
+ }
+ }
+ // Extend plugins by other plugins
+ for (PluginMetadata pluginMetadata : metadata) {
+ String pluginKey = pluginMetadata.getKey();
+ String basePluginKey = pluginMetadata.getBasePlugin();
+ if (StringUtils.isNotEmpty(pluginMetadata.getBasePlugin())) {
+ if (classLoaders.get(basePluginKey) != null) {
+ Logs.INFO.debug("Plugin {} extends {}", pluginKey, basePluginKey);
+ extend(basePluginKey, pluginKey, pluginMetadata.getDeployedFiles());
+ created.add(pluginMetadata);
+ } else {
+ // Ignored, because base plugin doesn't exists
+ Logs.INFO.warn("Plugin {} extends nonexistent plugin {}", pluginKey, basePluginKey);
+ }
+ }
+ }
classLoaders.done();
+ return created;
}
}
*/
package org.sonar.server.plugins;
+import java.io.File;
+import java.io.IOException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.io.FileUtils;
import org.sonar.server.platform.ServerStartException;
import org.sonar.updatecenter.common.PluginKeyUtils;
-import java.io.File;
-import java.io.IOException;
-import java.net.URL;
-import java.net.URLClassLoader;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
public final class PluginDeployer implements ServerComponent {
private static final Logger LOG = LoggerFactory.getLogger(PluginDeployer.class);
public List<String> getUninstalls() {
List<String> names = Lists.newArrayList();
if (fileSystem.getRemovedPluginsDir().exists()) {
- List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[]{"jar"}, false);
+ List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[] { "jar" }, false);
for (File file : files) {
names.add(file.getName());
}
public void cancelUninstalls() {
if (fileSystem.getRemovedPluginsDir().exists()) {
- List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[]{"jar"}, false);
+ List<File> files = (List<File>) FileUtils.listFiles(fileSystem.getRemovedPluginsDir(), new String[] { "jar" }, false);
for (File file : files) {
try {
FileUtils.moveFileToDirectory(file, fileSystem.getUserPluginsDir(), false);
}
FileUtils.deleteQuietly(tempDir);
}
- classloaders.create(plugin);
+ classloaders.addForCreation(plugin);
} catch (IOException e) {
throw new RuntimeException("Fail to deploy the plugin " + plugin, e);
private void moveAndLoadDownloadedPlugins() throws IOException {
if (fileSystem.getDownloadedPluginsDir().exists()) {
- Collection<File> jars = FileUtils.listFiles(fileSystem.getDownloadedPluginsDir(), new String[]{"jar"}, false);
+ Collection<File> jars = FileUtils.listFiles(fileSystem.getDownloadedPluginsDir(), new String[] { "jar" }, false);
for (File jar : jars) {
File movedJar = moveDownloadedFile(jar);
if (movedJar != null) {
String mainClass = plugin.getMainClass();
try {
- URLClassLoader pluginClassLoader = URLClassLoader.newInstance(new URL[]{tempFile.toURI().toURL()}, getClass().getClassLoader());
+ URLClassLoader pluginClassLoader = URLClassLoader.newInstance(new URL[] { tempFile.toURI().toURL() }, getClass().getClassLoader());
Plugin pluginInstance = (Plugin) pluginClassLoader.loadClass(mainClass).newInstance();
plugin.setKey(PluginKeyUtils.sanitize(pluginInstance.getKey()));
plugin.setDescription(pluginInstance.getDescription());
*/
package org.sonar.server.plugins;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.sonar.core.plugin.JpaPlugin;
import org.sonar.updatecenter.common.PluginManifest;
-import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
/**
* @since 2.2
*/
private String homepage;
private boolean core;
private boolean useChildFirstClassLoader;
+ private String basePlugin;
private String[] dependencyPaths = new String[0];
public List<File> deployedFiles = new ArrayList<File>();
return useChildFirstClassLoader;
}
+ public void setBasePlugin(String key) {
+ this.basePlugin = key;
+ }
+
+ public String getBasePlugin() {
+ return basePlugin;
+ }
+
public void setDependencyPaths(String[] paths) {
this.dependencyPaths = paths;
}
metadata.setDependencyPaths(manifest.getDependencies());
metadata.setCore(corePlugin);
metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
+ metadata.setBasePlugin(manifest.getExtendPlugin());
return metadata;
}
jpaPlugin.setHomepage(getHomepage());
jpaPlugin.setCore(isCore());
jpaPlugin.setUseChildFirstClassLoader(isUseChildFirstClassLoader());
+ jpaPlugin.setBasePlugin(getBasePlugin());
jpaPlugin.removeFiles();
for (File file : getDeployedFiles()) {
jpaPlugin.createFile(file.getName());
*/
package org.sonar.server.plugins;
+import java.util.List;
+
import org.picocontainer.Characteristics;
import org.picocontainer.MutablePicoContainer;
import org.picocontainer.PicoContainer;
import org.sonar.api.ServerExtension;
import org.sonar.api.utils.SonarException;
import org.sonar.core.plugin.AbstractPluginRepository;
-import org.sonar.core.plugin.JpaPlugin;
-import org.sonar.core.plugin.JpaPluginDao;
/**
* @since 2.2
*/
public class ServerPluginRepository extends AbstractPluginRepository {
- private JpaPluginDao dao;
private PluginClassLoaders classloaders;
- public ServerPluginRepository(JpaPluginDao dao, PluginClassLoaders classloaders) {
- this.dao = dao;
+ public ServerPluginRepository(PluginClassLoaders classloaders) {
this.classloaders = classloaders;
}
public void registerPlugins(MutablePicoContainer pico) {
// Create ClassLoaders
- for (JpaPlugin jpaPlugin : dao.getPlugins()) {
- classloaders.getClassLoader(jpaPlugin.getKey());
- }
- classloaders.done();
+ List<PluginMetadata> register = classloaders.completeCreation();
// Register plugins
- for (JpaPlugin jpaPlugin : dao.getPlugins()) {
+ for (PluginMetadata pluginMetadata : register) {
try {
- Class pluginClass = classloaders.getClassLoader(jpaPlugin.getKey()).loadClass(jpaPlugin.getPluginClass());
+ Class pluginClass = classloaders.getClassLoader(pluginMetadata.getKey()).loadClass(pluginMetadata.getMainClass());
pico.as(Characteristics.CACHE).addComponent(pluginClass);
Plugin plugin = (Plugin) pico.getComponent(pluginClass);
- registerPlugin(pico, plugin, jpaPlugin.getKey());
+ registerPlugin(pico, plugin, pluginMetadata.getKey());
} catch (ClassNotFoundException e) {
- throw new SonarException("Please check the plugin manifest. The main plugin class does not exist: " + jpaPlugin.getPluginClass(), e);
+ throw new SonarException(
+ "Please check the plugin manifest. The main plugin class does not exist: " + pluginMetadata.getMainClass(), e);
}
}
invokeExtensionProviders(pico);
--- /dev/null
+#
+# Sonar, entreprise quality control tool.
+# Copyright (C) 2008-2011 SonarSource
+# mailto:contact AT sonarsource DOT com
+#
+# Sonar 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.
+#
+# Sonar 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 Sonar; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+#
+
+#
+# Sonar 2.6
+#
+class AddPluginBase < ActiveRecord::Migration
+
+ def self.up
+ add_column 'plugins', 'base_plugin', :string, :limit => 100, :null => true
+ Plugin.reset_column_information
+ end
+
+end
import static junit.framework.Assert.assertNotNull;
import static junit.framework.Assert.assertNull;
-import org.junit.Test;
-import org.sonar.test.TestUtils;
-
-import com.google.common.collect.Lists;
-
import java.io.File;
import java.io.IOException;
+import com.google.common.collect.Lists;
+import org.junit.Test;
+import org.sonar.test.TestUtils;
+
public class PluginClassLoadersTest {
@Test
assertNull(getClass().getClassLoader().getResource("foo.txt"));
PluginClassLoaders classloaders = new PluginClassLoaders();
- ClassLoader classloader = classloaders.create(metadata);
+ ClassLoader classloader = classloaders.create(metadata.getKey(), metadata.getDeployedFiles(), metadata.isUseChildFirstClassLoader());
assertNotNull(classloader);
assertNotNull(classloader.getResource("foo.txt"));
assertThat(deployedJar.isFile(), is(true));
// check that the plugin has its own classloader
+ classloaders.completeCreation();
ClassLoader classloader = classloaders.getClassLoader("foo");
assertNotNull(classloader);
}
assertThat(deployedJar.isFile(), is(true));
// check that the plugin has its own classloader
+ classloaders.completeCreation();
ClassLoader classloader = classloaders.getClassLoader("buildbreaker");
assertNotNull(classloader);
assertNotNull(classloader.loadClass("org.sonar.plugins.buildbreaker.BuildBreakerPlugin"));
assertThat(deployedJar.isFile(), is(true));
// check that the extension is in the classloader
+ classloaders.completeCreation();
ClassLoader classloader = classloaders.getClassLoader("foo");
File extensionFile = FileUtils.toFile(classloader.getResource("foo-extension.txt"));
assertThat(extensionFile.exists(), is(true));