Browse Source

SONAR-1838: Allow inter-dependencies for plugins

tags/2.6
Godin 13 years ago
parent
commit
fa92a9ff62

+ 14
- 16
sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java View File

@@ -19,7 +19,6 @@
*/
package org.sonar.batch;

import com.google.common.collect.HashMultimap;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
@@ -33,25 +32,27 @@ import org.sonar.api.batch.AbstractCoverageExtension;
import org.sonar.api.resources.Java;
import org.sonar.api.resources.Project;
import org.sonar.api.utils.SonarException;
import org.sonar.core.classloaders.ClassLoadersCollection;
import org.sonar.core.plugin.AbstractPluginRepository;
import org.sonar.core.plugin.JpaPlugin;
import org.sonar.core.plugin.JpaPluginDao;
import org.sonar.core.plugin.JpaPluginFile;

import com.google.common.collect.HashMultimap;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public class BatchPluginRepository extends AbstractPluginRepository {

private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class);

private Map<String, ClassLoader> classloaders;
private String baseUrl;
private JpaPluginDao dao;

private ClassLoadersCollection classLoaders;

public BatchPluginRepository(JpaPluginDao dao, ServerMetadata server) {
this.dao = dao;
this.baseUrl = server.getUrl() + "/deploy/plugins/";
@@ -68,7 +69,7 @@ public class BatchPluginRepository extends AbstractPluginRepository {
HashMultimap<String, URL> urlsByKey = HashMultimap.create();
for (JpaPluginFile pluginFile : dao.getPluginFiles()) {
try {
String key = getClassloaderKey(pluginFile.getPluginKey());
String key = pluginFile.getPluginKey();
URL url = new URL(baseUrl + pluginFile.getPath());
urlsByKey.put(key, url);

@@ -77,7 +78,7 @@ public class BatchPluginRepository extends AbstractPluginRepository {
}
}

classloaders = new HashMap<String, ClassLoader>();
classLoaders = new ClassLoadersCollection(Thread.currentThread().getContextClassLoader());
for (String key : urlsByKey.keySet()) {
Set<URL> urls = urlsByKey.get(key);

@@ -87,19 +88,16 @@ public class BatchPluginRepository extends AbstractPluginRepository {
LOG.debug(" -> " + url);
}
}
classloaders.put(key, new RemoteClassLoader(urls, Thread.currentThread().getContextClassLoader()).getClassLoader());
}
}

private String getClassloaderKey(String pluginKey) {
return "sonar-plugin-" + pluginKey;
classLoaders.createClassLoader(key, urls);
}
classLoaders.done();
}

public void registerPlugins(MutablePicoContainer pico) {
for (JpaPlugin pluginMetadata : dao.getPlugins()) {
try {
String classloaderKey = getClassloaderKey(pluginMetadata.getKey());
Class claz = classloaders.get(classloaderKey).loadClass(pluginMetadata.getPluginClass());
Class claz = classLoaders.get(pluginMetadata.getKey()).loadClass(pluginMetadata.getPluginClass());
Plugin plugin = (Plugin) claz.newInstance();
registerPlugin(pico, plugin, pluginMetadata.getKey());

@@ -125,7 +123,7 @@ public class BatchPluginRepository extends AbstractPluginRepository {
boolean ok = isType(extension, BatchExtension.class);
if (ok && isType(extension, AbstractCoverageExtension.class)) {
ok = shouldRegisterCoverageExtension(pluginKey, container.getComponent(Project.class), container.getComponent(Configuration.class));
if (!ok) {
if ( !ok) {
LOG.debug("The following extension is ignored: " + extension + ". See the parameter " + AbstractCoverageExtension.PARAM_PLUGIN);
}
}
@@ -133,11 +131,11 @@ public class BatchPluginRepository extends AbstractPluginRepository {
}

boolean shouldRegisterCoverageExtension(String pluginKey, Project project, Configuration conf) {
boolean ok=true;
boolean ok = true;
if (StringUtils.equals(project.getLanguageKey(), Java.KEY)) {
String[] selectedPluginKeys = conf.getStringArray(AbstractCoverageExtension.PARAM_PLUGIN);
if (ArrayUtils.isEmpty(selectedPluginKeys)) {
selectedPluginKeys = new String[]{AbstractCoverageExtension.DEFAULT_PLUGIN};
selectedPluginKeys = new String[] { AbstractCoverageExtension.DEFAULT_PLUGIN };
}
String oldCoveragePluginKey = getOldCoveragePluginKey(pluginKey);
ok = ArrayUtils.contains(selectedPluginKeys, pluginKey) || ArrayUtils.contains(selectedPluginKeys, oldCoveragePluginKey);

+ 100
- 0
sonar-core/src/main/java/org/sonar/core/classloaders/ClassLoadersCollection.java View File

@@ -0,0 +1,100 @@
package org.sonar.core.classloaders;

import org.apache.commons.lang.StringUtils;
import org.codehaus.classworlds.ClassRealm;
import org.codehaus.classworlds.ClassWorld;
import org.codehaus.classworlds.DuplicateRealmException;
import org.codehaus.classworlds.NoSuchRealmException;
import org.sonar.api.utils.Logs;
import org.sonar.api.utils.SonarException;

import java.net.URL;
import java.util.Arrays;
import java.util.Collection;

/**
* EXPERIMENTAL!
*
* Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies.
* Current implementation based on {@link ClassWorld}.
*
* <h3>IMPORTANT</h3>
* <ul>
* <li>If we have pluginA , then all classes and resources from packages and subpackages of org.sonar.plugins.pluginA will be visible for
* all other plugins.</li>
* <li>If pluginA depends on lib.jar which contains org.sonar.plugins.pluginA.SomeClass , then SomeClass will be visible for all other
* plugins.</li>
* </ul>
*
* @since 2.4
*/
public class ClassLoadersCollection {

private static final String EXPORTED_PREFIX = "org.sonar.plugins.";

private ClassWorld world = new ClassWorld();
private ClassLoader baseClassLoader;

public ClassLoadersCollection(ClassLoader baseClassLoader) {
this.baseClassLoader = baseClassLoader;
}

/**
* Generates URLClassLoaders, which use the parent first delegation mode.
* Actually this method shouldn't return anything, because dependencies must be established - see {@link #done()}.
*/
public ClassLoader createClassLoader(String key, Collection<URL> urls) {
try {
ClassRealm realm = world.newRealm(key, baseClassLoader);
for (URL constituent : urls) {
realm.addConstituent(constituent);
}
// export(realm, EXPORTED_PREFIX + key);
return realm.getClassLoader();
} catch (DuplicateRealmException e) {
throw new SonarException(e);
}
}

/**
* Establishes dependencies among ClassLoaders.
*/
public void done() {
for (Object o : world.getRealms()) {
ClassRealm realm = (ClassRealm) o;
export(realm, EXPORTED_PREFIX + realm.getId());
}
}

/**
* Exports specified packages from given ClassRealm to all others.
*/
private void export(ClassRealm realm, String... packages) {
Logs.INFO.info("Exporting " + Arrays.toString(packages) + " from " + realm.getId());
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) {
try {
return world.getRealm(key).getClassLoader();
} catch (NoSuchRealmException e) {
return null;
}
}

}

+ 15
- 23
sonar-server/src/main/java/org/sonar/server/plugins/PluginClassLoaders.java View File

@@ -19,25 +19,21 @@
*/
package org.sonar.server.plugins;

import com.google.common.collect.Maps;
import org.apache.commons.lang.StringUtils;
import org.codehaus.classworlds.ClassRealm;
import org.codehaus.classworlds.ClassWorld;
import org.codehaus.classworlds.DuplicateRealmException;
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.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.List;

public class PluginClassLoaders implements ServerComponent {

private Map<String, ClassLoader> classLoaderByPluginKey = Maps.newHashMap();
private ClassWorld world = new ClassWorld();
private ClassLoadersCollection classLoaders = new ClassLoadersCollection(getClass().getClassLoader());

public ClassLoader create(PluginMetadata plugin) {
return create(plugin.getKey(), plugin.getDeployedFiles());
@@ -45,24 +41,16 @@ public class PluginClassLoaders implements ServerComponent {

ClassLoader create(String pluginKey, Collection<File> classloaderFiles) {
try {
ClassRealm realm;
realm = world.newRealm(pluginKey, getClass().getClassLoader());
List<URL> urls = new ArrayList<URL>();
for (File file : classloaderFiles) {
realm.addConstituent(toUrl(file));
urls.add(toUrl(file));
}
ClassLoader classloader = realm.getClassLoader();
classLoaderByPluginKey.put(pluginKey, classloader);
return classloader;

} catch (DuplicateRealmException e) {
throw new RuntimeException("Fail to load the classloader of the plugin: " + pluginKey, e);

return classLoaders.createClassLoader(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
@@ -71,22 +59,22 @@ public class PluginClassLoaders implements ServerComponent {
// Otherwise the constituent will be treated as a JAR file.
URL url = file.toURI().toURL();
if (file.isDirectory()) {
if (!url.toString().endsWith("/")) {
if ( !url.toString().endsWith("/")) {
url = new URL(url.toString() + "/");
}
} else if (!StringUtils.endsWithIgnoreCase(file.getName(), "jar")) {
} else if ( !StringUtils.endsWithIgnoreCase(file.getName(), "jar")) {
url = file.getParentFile().toURI().toURL();
}
return url;
}

public ClassLoader getClassLoader(String pluginKey) {
return classLoaderByPluginKey.get(pluginKey);
return classLoaders.get(pluginKey);
}

public Class getClass(String pluginKey, String classname) {
Class clazz = null;
ClassLoader classloader = classLoaderByPluginKey.get(pluginKey);
ClassLoader classloader = getClassLoader(pluginKey);
if (classloader != null) {
try {
clazz = classloader.loadClass(classname);
@@ -97,4 +85,8 @@ public class PluginClassLoaders implements ServerComponent {
}
return clazz;
}

public void done() {
classLoaders.done();
}
}

+ 6
- 1
sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java View File

@@ -49,6 +49,12 @@ public class ServerPluginRepository extends AbstractPluginRepository {
}

public void registerPlugins(MutablePicoContainer pico) {
// Create ClassLoaders
for (JpaPlugin jpaPlugin : dao.getPlugins()) {
classloaders.getClassLoader(jpaPlugin.getKey());
}
classloaders.done();
// Register plugins
for (JpaPlugin jpaPlugin : dao.getPlugins()) {
try {
Class pluginClass = classloaders.getClassLoader(jpaPlugin.getKey()).loadClass(jpaPlugin.getPluginClass());
@@ -56,7 +62,6 @@ public class ServerPluginRepository extends AbstractPluginRepository {
Plugin plugin = (Plugin) pico.getComponent(pluginClass);
registerPlugin(pico, plugin, jpaPlugin.getKey());


} catch (ClassNotFoundException e) {
throw new SonarException("Please check the plugin manifest. The main plugin class does not exist: " + jpaPlugin.getPluginClass(), e);
}

Loading…
Cancel
Save