]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-1838: Allow inter-dependencies for plugins
authorGodin <mandrikov@gmail.com>
Fri, 15 Oct 2010 00:08:24 +0000 (00:08 +0000)
committerGodin <mandrikov@gmail.com>
Fri, 15 Oct 2010 00:08:24 +0000 (00:08 +0000)
sonar-batch/src/main/java/org/sonar/batch/BatchPluginRepository.java
sonar-core/src/main/java/org/sonar/core/classloaders/ClassLoadersCollection.java [new file with mode: 0644]
sonar-server/src/main/java/org/sonar/server/plugins/PluginClassLoaders.java
sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java

index acfc42b355ff8040e1ca38403df0a621b5440885..e831faaee99b6f44b59b868f6e910f4294d13159 100644 (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);
diff --git a/sonar-core/src/main/java/org/sonar/core/classloaders/ClassLoadersCollection.java b/sonar-core/src/main/java/org/sonar/core/classloaders/ClassLoadersCollection.java
new file mode 100644 (file)
index 0000000..858ed05
--- /dev/null
@@ -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;
+    }
+  }
+
+}
index 09d963723140ceff284a614bddeb519bdaf4629e..c819bc3a623b570d0a8374eefab23db6137c8372 100644 (file)
  */
 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();
+  }
 }
index bf14cd42349b8bdc97e4b35bf5365b1f65fbb021..5cc2def52d25d0f995d3ee5ad4f19d1cd45e4a96 100644 (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);
       }