001 /* 002 * Sonar, open source software quality management tool. 003 * Copyright (C) 2009 SonarSource SA 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.classloaders; 021 022 import com.google.common.collect.Lists; 023 import org.apache.commons.lang.StringUtils; 024 import org.codehaus.classworlds.ClassRealm; 025 import org.codehaus.classworlds.ClassWorld; 026 import org.codehaus.classworlds.DuplicateRealmException; 027 import org.codehaus.classworlds.NoSuchRealmException; 028 import org.sonar.api.utils.Logs; 029 import org.sonar.api.utils.SonarException; 030 031 import java.net.URL; 032 import java.util.Arrays; 033 import java.util.Collection; 034 import java.util.List; 035 036 /** 037 * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. 038 * Current implementation based on {@link ClassWorld}. 039 * 040 * <h3>IMPORTANT</h3> 041 * <p> 042 * If we have pluginA , then all classes and resources from package and subpackages of <b>org.sonar.plugins.pluginA.api</b> will be visible 043 * for all other plugins even if they located in dependent library. 044 * </p> 045 * 046 * <h4>Search order for {@link ClassRealm} :</h4> 047 * <ul> 048 * <li>parent class loader (passed via the constructor) if there is one</li> 049 * <li>imports</li> 050 * <li>realm's constituents</li> 051 * <li>parent realm</li> 052 * </ul> 053 * 054 * @since 2.4 055 */ 056 public class ClassLoadersCollection { 057 058 private static final String[] PREFIXES_TO_EXPORT = { "org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins." }; 059 060 private ClassWorld world = new ClassWorld(); 061 private ClassLoader baseClassLoader; 062 063 public ClassLoadersCollection(ClassLoader baseClassLoader) { 064 this.baseClassLoader = baseClassLoader; 065 } 066 067 /** 068 * Generates URLClassLoader with specified delegation model. 069 * 070 * @param key plugin key 071 * @param urls libraries 072 * @param childFirst true, if child-first delegation model required instead of parent-first 073 * @return created ClassLoader, but actually this method shouldn't return anything, 074 * because dependencies must be established - see {@link #done()}. 075 */ 076 public ClassLoader createClassLoader(String key, Collection<URL> urls, boolean childFirst) { 077 try { 078 List<URL> resources = Lists.newArrayList(); 079 List<URL> others = Lists.newArrayList(); 080 for (URL url : urls) { 081 if (isResource(url)) { 082 resources.add(url); 083 } else { 084 others.add(url); 085 } 086 } 087 ClassLoader parent; 088 if (resources.isEmpty()) { 089 parent = baseClassLoader; 090 } else { 091 parent = new ResourcesClassLoader(resources, baseClassLoader); 092 } 093 final ClassRealm realm; 094 if (childFirst) { 095 ClassRealm parentRealm = world.newRealm(key + "-parent", parent); 096 realm = parentRealm.createChildRealm(key); 097 } else { 098 realm = world.newRealm(key, parent); 099 } 100 for (URL url : others) { 101 realm.addConstituent(url); 102 } 103 return realm.getClassLoader(); 104 } catch (DuplicateRealmException e) { 105 throw new SonarException(e); 106 } 107 } 108 109 /** 110 * Establishes dependencies among ClassLoaders. 111 */ 112 public void done() { 113 for (Object o : world.getRealms()) { 114 ClassRealm realm = (ClassRealm) o; 115 if ( !StringUtils.endsWith(realm.getId(), "-parent")) { 116 String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; 117 for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { 118 // important to have dot at the end of package name 119 packagesToExport[i] = PREFIXES_TO_EXPORT[i] + realm.getId() + ".api."; 120 } 121 export(realm, packagesToExport); 122 } 123 } 124 } 125 126 /** 127 * Exports specified packages from given ClassRealm to all others. 128 */ 129 private void export(ClassRealm realm, String... packages) { 130 Logs.INFO.debug("Exporting " + Arrays.toString(packages) + " from " + realm.getId()); 131 for (Object o : world.getRealms()) { 132 ClassRealm dep = (ClassRealm) o; 133 if ( !StringUtils.equals(dep.getId(), realm.getId())) { 134 try { 135 for (String packageName : packages) { 136 dep.importFrom(realm.getId(), packageName); 137 } 138 } catch (NoSuchRealmException e) { 139 // should never happen 140 throw new SonarException(e); 141 } 142 } 143 } 144 } 145 146 /** 147 * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. 148 */ 149 public ClassLoader get(String key) { 150 try { 151 return world.getRealm(key).getClassLoader(); 152 } catch (NoSuchRealmException e) { 153 return null; 154 } 155 } 156 157 private boolean isResource(URL url) { 158 String path = url.getPath(); 159 return !StringUtils.endsWith(path, ".jar") && !StringUtils.endsWith(path, "/"); 160 } 161 162 }