001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2008-2012 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * Sonar is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * Sonar is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public 017 * License along with Sonar; if not, write to the Free Software 018 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02 019 */ 020 package org.sonar.core.plugins; 021 022 import com.google.common.collect.Lists; 023 import com.google.common.collect.Maps; 024 import org.apache.commons.lang.StringUtils; 025 import org.codehaus.plexus.classworlds.ClassWorld; 026 import org.codehaus.plexus.classworlds.realm.ClassRealm; 027 import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; 028 import org.slf4j.Logger; 029 import org.slf4j.LoggerFactory; 030 import org.sonar.api.Plugin; 031 import org.sonar.api.platform.PluginMetadata; 032 import org.sonar.api.utils.SonarException; 033 034 import java.io.File; 035 import java.net.URL; 036 import java.util.Collection; 037 import java.util.List; 038 import java.util.Map; 039 040 /** 041 * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on 042 * {@link ClassWorld}. 043 * <p/> 044 * <h3>IMPORTANT</h3> 045 * <p> 046 * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible 047 * for all other plugins even if they located in dependent library. 048 * </p> 049 * <p/> 050 * <h4>Search order for {@link ClassRealm} :</h4> 051 * <ul> 052 * <li>parent class loader (passed via the constructor) if there is one</li> 053 * <li>imports</li> 054 * <li>realm's constituents</li> 055 * <li>parent realm</li> 056 * </ul> 057 */ 058 public class PluginClassloaders { 059 060 private static final String[] PREFIXES_TO_EXPORT = {"org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins."}; 061 private static final Logger LOG = LoggerFactory.getLogger(PluginClassloaders.class); 062 063 private ClassWorld world = new ClassWorld(); 064 private ClassLoader baseClassloader; 065 private boolean done = false; 066 067 public PluginClassloaders(ClassLoader baseClassloader) { 068 this.baseClassloader = baseClassloader; 069 } 070 071 public Map<String, Plugin> init(Collection<PluginMetadata> plugins) { 072 List<PluginMetadata> children = Lists.newArrayList(); 073 for (PluginMetadata plugin : plugins) { 074 if (StringUtils.isBlank(plugin.getBasePlugin())) { 075 add(plugin); 076 } else { 077 children.add(plugin); 078 } 079 } 080 081 for (PluginMetadata child : children) { 082 extend(child); 083 } 084 085 done(); 086 087 Map<String, Plugin> pluginsByKey = Maps.newHashMap(); 088 for (PluginMetadata metadata : plugins) { 089 pluginsByKey.put(metadata.getKey(), instantiatePlugin(metadata)); 090 } 091 return pluginsByKey; 092 } 093 094 public ClassLoader add(PluginMetadata plugin) { 095 if (done) { 096 throw new IllegalStateException("Plugin classloaders are already initialized"); 097 } 098 try { 099 List<URL> resources = Lists.newArrayList(); 100 List<URL> others = Lists.newArrayList(); 101 for (File file : plugin.getDeployedFiles()) { 102 if (isResource(file)) { 103 resources.add(file.toURI().toURL()); 104 } else { 105 others.add(file.toURI().toURL()); 106 } 107 } 108 ClassLoader parent; 109 if (resources.isEmpty()) { 110 parent = baseClassloader; 111 } else { 112 parent = new ResourcesClassloader(resources, baseClassloader); 113 } 114 final ClassRealm realm; 115 if (plugin.isUseChildFirstClassLoader()) { 116 ClassRealm parentRealm = world.newRealm(plugin.getKey() + "-parent", parent); 117 realm = parentRealm.createChildRealm(plugin.getKey()); 118 } else { 119 realm = world.newRealm(plugin.getKey(), parent); 120 } 121 for (URL url : others) { 122 realm.addURL(url); 123 } 124 return realm; 125 } catch (Exception e) { 126 throw new SonarException(e); 127 } 128 } 129 130 public boolean extend(PluginMetadata plugin) { 131 if (done) { 132 throw new IllegalStateException("Plugin classloaders are already initialized"); 133 } 134 try { 135 ClassRealm base = world.getRealm(plugin.getBasePlugin()); 136 if (base == null) { 137 // Ignored, because base plugin is not installed 138 LOG.warn("Plugin " + plugin.getKey() + " is ignored because base plugin is not installed: " + plugin.getBasePlugin()); 139 return false; 140 } 141 base.createChildRealm(plugin.getKey()); // we create new realm to be able to return it by key without conversion to baseKey 142 for (File file : plugin.getDeployedFiles()) { 143 base.addURL(file.toURI().toURL()); 144 } 145 return true; 146 } catch (Exception e) { 147 throw new SonarException(e); 148 } 149 } 150 151 /** 152 * Establishes dependencies among ClassLoaders. 153 */ 154 public void done() { 155 if (done) { 156 throw new IllegalStateException("Plugin classloaders are already initialized"); 157 } 158 for (Object o : world.getRealms()) { 159 ClassRealm realm = (ClassRealm) o; 160 if (!StringUtils.endsWith(realm.getId(), "-parent")) { 161 String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; 162 for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { 163 // important to have dot at the end of package name only for classworlds 1.1 164 packagesToExport[i] = PREFIXES_TO_EXPORT[i] + realm.getId() + ".api"; 165 } 166 export(realm, packagesToExport); 167 } 168 } 169 done = true; 170 } 171 172 /** 173 * Exports specified packages from given ClassRealm to all others. 174 */ 175 private void export(ClassRealm realm, String... packages) { 176 for (Object o : world.getRealms()) { 177 ClassRealm dep = (ClassRealm) o; 178 if (!StringUtils.equals(dep.getId(), realm.getId())) { 179 try { 180 for (String packageName : packages) { 181 dep.importFrom(realm.getId(), packageName); 182 } 183 } catch (NoSuchRealmException e) { 184 // should never happen 185 throw new SonarException(e); 186 } 187 } 188 } 189 } 190 191 /** 192 * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. 193 */ 194 public ClassLoader get(String key) { 195 if (!done) { 196 throw new IllegalStateException("Plugin classloaders are not initialized"); 197 } 198 try { 199 return world.getRealm(key); 200 } catch (NoSuchRealmException e) { 201 return null; 202 } 203 } 204 205 public Plugin instantiatePlugin(PluginMetadata metadata) { 206 try { 207 Class claz = get(metadata.getKey()).loadClass(metadata.getMainClass()); 208 return (Plugin) claz.newInstance(); 209 210 } catch (Exception e) { 211 throw new SonarException("Fail to load plugin " + metadata.getKey(), e); 212 } 213 } 214 215 private boolean isResource(File file) { 216 return !StringUtils.endsWithIgnoreCase(file.getName(), ".jar") && !file.isDirectory(); 217 } 218 219 public void clean() { 220 for (ClassRealm realm : (Collection<ClassRealm>) world.getRealms()) { 221 try { 222 world.disposeRealm(realm.getId()); 223 } catch (Exception e) { 224 // Ignore 225 } 226 world=null; 227 } 228 } 229 }