@@ -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); |
@@ -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; | |||
} | |||
} | |||
} |
@@ -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(); | |||
} | |||
} |
@@ -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); | |||
} |