*/
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;
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/";
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);
}
}
- classloaders = new HashMap<String, ClassLoader>();
+ classLoaders = new ClassLoadersCollection(Thread.currentThread().getContextClassLoader());
for (String key : urlsByKey.keySet()) {
Set<URL> urls = urlsByKey.get(key);
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());
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);
}
}
}
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);
--- /dev/null
+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;
+ }
+ }
+
+}
*/
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());
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
// 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);
}
return clazz;
}
+
+ public void done() {
+ classLoaders.done();
+ }
}
}
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());
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);
}